diff --git a/.tool-versions b/.tool-versions index 441b479d..cc60fd62 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -scarb 2.6.4 \ No newline at end of file +scarb 2.8.2 diff --git a/README.md b/README.md index 9a030f52..17c6f445 100644 --- a/README.md +++ b/README.md @@ -9,15 +9,15 @@ ## Running Tests -The original codebase uses [Scarb](https://docs.swmansion.com/scarb/) (2.6.4) to build and test the contracts. Be sure to setup [asdf](https://asdf-vm.com/) as well, to handle versioning. +The original codebase uses [Scarb](https://docs.swmansion.com/scarb/) (2.8.2) to build and test the contracts. Be sure to setup [asdf](https://asdf-vm.com/) as well, to handle versioning. To ensure you are setup, run the following command from the root of this directory and check the output matches: ``` ❯ scarb --version -scarb 2.6.4 (c4c7c0bac 2024-03-19) -cairo: 2.6.3 (https://crates.io/crates/cairo-lang-compiler/2.6.3) -sierra: 1.5.0 +scarb 2.8.2 (a37b4cbfc 2024-09-09) +cairo: 2.8.2 (https://crates.io/crates/cairo-lang-compiler/2.8.2) +sierra: 1.6.0 ``` Once Scarb is setup, you can run the full test suite via: diff --git a/Scarb.lock b/Scarb.lock index 16945a56..f4402594 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -2,13 +2,62 @@ version = 1 [[package]] -name = "openzeppelin" -version = "0.11.0" -source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.11.0#a83f36b23f1af6e160288962be4a2701c3ecbcda" +name = "openzeppelin_access" +version = "0.16.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:b9139449e53d715992b2d9e887c0c723d886419bee7ceb5561648c70bd6d3174" +dependencies = [ + "openzeppelin_introspection", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_account" +version = "0.16.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:841bb881adbe98b64fee1dc1329f6e3fbabdfbd9fa65c66ffb54c55f13764bce" +dependencies = [ + "openzeppelin_introspection", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_governance" +version = "0.16.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:a9d9c983cfd4369e7bbb69433fb264edf376805ed873b1f70a287825a6bd1eaf" +dependencies = [ + "openzeppelin_access", + "openzeppelin_introspection", +] + +[[package]] +name = "openzeppelin_introspection" +version = "0.16.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:312bc2e531f036480ad7392dbb31042c38d875ef9dbb5578ea8de5c05e35b7d8" + +[[package]] +name = "openzeppelin_token" +version = "0.16.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:7074c23fbc300b3cccff1037264dfdbe976fb11ae42ce687f4a8ce469adc552a" +dependencies = [ + "openzeppelin_account", + "openzeppelin_governance", + "openzeppelin_introspection", +] + +[[package]] +name = "openzeppelin_utils" +version = "0.16.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:a494aeb5f1371db7f22e922196aa41d1d1698877a766a838350c0b6ffe49fda2" [[package]] -name = "pitch_lake_starknet" +name = "pitch_lake" version = "0.1.0" dependencies = [ - "openzeppelin", + "openzeppelin_token", + "openzeppelin_utils", ] diff --git a/Scarb.toml b/Scarb.toml index a7a0ab60..9524ce18 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -1,18 +1,18 @@ [package] -name = "pitch_lake_starknet" +name = "pitch_lake" description = "Oiler PitchLake" version = "0.1.0" -cairo-version = "2.2.0" - -# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest [[target.starknet-contract]] sierra = true casm = true [dependencies] -starknet = "2.1.0" -openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.11.0" } +starknet = "2.8.2" +openzeppelin_token = "0.16.0" +openzeppelin_utils = "0.16.0" + +[dev-dependencies] +cairo_test = "2.8.2" [tool.snforge] -# exit_first = true diff --git a/cairo_project.toml b/cairo_project.toml deleted file mode 100644 index 18278bad..00000000 --- a/cairo_project.toml +++ /dev/null @@ -1,3 +0,0 @@ -[crate_roots] -pitch_lake_starknet = "src" -tests = "tests" \ No newline at end of file diff --git a/scripts/abi/eth.ts b/scripts/abi/eth.ts index 8037db11..dc2c0035 100644 --- a/scripts/abi/eth.ts +++ b/scripts/abi/eth.ts @@ -2,7 +2,7 @@ export const ABI = [ { "type": "impl", "name": "ERC20MixinImpl", - "interface_name": "openzeppelin::token::erc20::interface::ERC20ABI" + "interface_name": "openzeppelin_token::erc20::interface::ERC20ABI" }, { "type": "struct", @@ -52,7 +52,7 @@ export const ABI = [ }, { "type": "interface", - "name": "openzeppelin::token::erc20::interface::ERC20ABI", + "name": "openzeppelin_token::erc20::interface::ERC20ABI", "items": [ { "type": "function", @@ -267,7 +267,7 @@ export const ABI = [ }, { "type": "event", - "name": "openzeppelin::token::erc20::erc20::ERC20Component::Transfer", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Transfer", "kind": "struct", "members": [ { @@ -289,7 +289,7 @@ export const ABI = [ }, { "type": "event", - "name": "openzeppelin::token::erc20::erc20::ERC20Component::Approval", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Approval", "kind": "struct", "members": [ { @@ -311,29 +311,29 @@ export const ABI = [ }, { "type": "event", - "name": "openzeppelin::token::erc20::erc20::ERC20Component::Event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Event", "kind": "enum", "variants": [ { "name": "Transfer", - "type": "openzeppelin::token::erc20::erc20::ERC20Component::Transfer", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Transfer", "kind": "nested" }, { "name": "Approval", - "type": "openzeppelin::token::erc20::erc20::ERC20Component::Approval", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Approval", "kind": "nested" } ] }, { "type": "event", - "name": "pitch_lake_starknet::library::eth::Eth::Event", + "name": "pitch_lake::library::eth::Eth::Event", "kind": "enum", "variants": [ { "name": "ERC20Event", - "type": "openzeppelin::token::erc20::erc20::ERC20Component::Event", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Event", "kind": "flat" } ] diff --git a/scripts/abi/factRegistry.ts b/scripts/abi/factRegistry.ts new file mode 100644 index 00000000..1ab14a1f --- /dev/null +++ b/scripts/abi/factRegistry.ts @@ -0,0 +1,102 @@ +export const ABI = [ + { + "type": "impl", + "name": "FactRegistryImpl", + "interface_name": "pitch_lake::fact_registry::interface::IFactRegistry" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "pitch_lake::fact_registry::interface::JobRequestParams", + "members": [ + { + "name": "twap", + "type": "(core::integer::u64, core::integer::u64)" + }, + { + "name": "volatility", + "type": "(core::integer::u64, core::integer::u64)" + }, + { + "name": "reserve_price", + "type": "(core::integer::u64, core::integer::u64)" + } + ] + }, + { + "type": "struct", + "name": "pitch_lake::fact_registry::interface::JobRequest", + "members": [ + { + "name": "identifiers", + "type": "core::array::Span::" + }, + { + "name": "params", + "type": "pitch_lake::fact_registry::interface::JobRequestParams" + } + ] + }, + { + "type": "interface", + "name": "pitch_lake::fact_registry::interface::IFactRegistry", + "items": [ + { + "type": "function", + "name": "get_fact", + "inputs": [ + { + "name": "job_id", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_fact", + "inputs": [ + { + "name": "job_request", + "type": "pitch_lake::fact_registry::interface::JobRequest" + }, + { + "name": "job_data", + "type": "core::array::Span::" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [] + }, + { + "type": "event", + "name": "pitch_lake::fact_registry::contract::FactRegistry::Event", + "kind": "enum", + "variants": [] + } +] as const; diff --git a/scripts/abi/index.ts b/scripts/abi/index.ts index 680ff1cd..a18480c0 100644 --- a/scripts/abi/index.ts +++ b/scripts/abi/index.ts @@ -1,4 +1,5 @@ -export { ABI as vaultABI} from "./vault" -export { ABI as erc20ABI} from "./erc20" -export { ABI as optionRoundABI} from "./optionRound" -export {ABI as marketAggregatorABI} from "./marketAggregator" \ No newline at end of file +export { ABI as vaultABI } from "./vault"; +export { ABI as erc20ABI } from "./erc20"; +export { ABI as optionRoundABI } from "./optionRound"; +export { ABI as factRegistryABI } from "./factRegistry"; + diff --git a/scripts/abi/marketAggregator.ts b/scripts/abi/marketAggregator.ts deleted file mode 100644 index 0d643562..00000000 --- a/scripts/abi/marketAggregator.ts +++ /dev/null @@ -1,277 +0,0 @@ -export const ABI = [ - { - "type": "impl", - "name": "MarketAggregatorImpl", - "interface_name": "pitch_lake_starknet::market_aggregator::interface::IMarketAggregator" - }, - { - "type": "struct", - "name": "core::integer::u256", - "members": [ - { - "name": "low", - "type": "core::integer::u128" - }, - { - "name": "high", - "type": "core::integer::u128" - } - ] - }, - { - "type": "enum", - "name": "core::option::Option::", - "variants": [ - { - "name": "Some", - "type": "core::integer::u256" - }, - { - "name": "None", - "type": "()" - } - ] - }, - { - "type": "enum", - "name": "core::option::Option::", - "variants": [ - { - "name": "Some", - "type": "core::integer::u128" - }, - { - "name": "None", - "type": "()" - } - ] - }, - { - "type": "interface", - "name": "pitch_lake_starknet::market_aggregator::interface::IMarketAggregator", - "items": [ - { - "type": "function", - "name": "get_TWAP_for_block_period", - "inputs": [ - { - "name": "from", - "type": "core::integer::u64" - }, - { - "name": "to", - "type": "core::integer::u64" - } - ], - "outputs": [ - { - "type": "core::option::Option::" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "get_TWAP_for_time_period", - "inputs": [ - { - "name": "from", - "type": "core::integer::u64" - }, - { - "name": "to", - "type": "core::integer::u64" - } - ], - "outputs": [ - { - "type": "core::option::Option::" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "get_reserve_price_for_round", - "inputs": [ - { - "name": "vault_address", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "round_id", - "type": "core::integer::u256" - } - ], - "outputs": [ - { - "type": "core::option::Option::" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "get_volatility_for_round", - "inputs": [ - { - "name": "vault_address", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "round_id", - "type": "core::integer::u256" - } - ], - "outputs": [ - { - "type": "core::option::Option::" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "get_cap_level_for_round", - "inputs": [ - { - "name": "vault_address", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "round_id", - "type": "core::integer::u256" - } - ], - "outputs": [ - { - "type": "core::option::Option::" - } - ], - "state_mutability": "view" - } - ] - }, - { - "type": "impl", - "name": "MarketAggregatorMock", - "interface_name": "pitch_lake_starknet::market_aggregator::interface::IMarketAggregatorMock" - }, - { - "type": "interface", - "name": "pitch_lake_starknet::market_aggregator::interface::IMarketAggregatorMock", - "items": [ - { - "type": "function", - "name": "set_TWAP_for_block_period", - "inputs": [ - { - "name": "from", - "type": "core::integer::u64" - }, - { - "name": "to", - "type": "core::integer::u64" - }, - { - "name": "TWAP", - "type": "core::integer::u256" - } - ], - "outputs": [], - "state_mutability": "external" - }, - { - "type": "function", - "name": "set_TWAP_for_time_period", - "inputs": [ - { - "name": "from", - "type": "core::integer::u64" - }, - { - "name": "to", - "type": "core::integer::u64" - }, - { - "name": "TWAP", - "type": "core::integer::u256" - } - ], - "outputs": [], - "state_mutability": "external" - }, - { - "type": "function", - "name": "set_reserve_price_for_round", - "inputs": [ - { - "name": "vault_address", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "round_id", - "type": "core::integer::u256" - }, - { - "name": "reserve_price", - "type": "core::integer::u256" - } - ], - "outputs": [], - "state_mutability": "external" - }, - { - "type": "function", - "name": "set_volatility_for_round", - "inputs": [ - { - "name": "vault_address", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "round_id", - "type": "core::integer::u256" - }, - { - "name": "vol", - "type": "core::integer::u128" - } - ], - "outputs": [], - "state_mutability": "external" - }, - { - "type": "function", - "name": "set_cap_level_for_round", - "inputs": [ - { - "name": "vault_address", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "round_id", - "type": "core::integer::u256" - }, - { - "name": "cap_level", - "type": "core::integer::u128" - } - ], - "outputs": [], - "state_mutability": "external" - } - ] - }, - { - "type": "constructor", - "name": "constructor", - "inputs": [] - }, - { - "type": "event", - "name": "pitch_lake_starknet::market_aggregator::contract::MarketAggregator::Event", - "kind": "enum", - "variants": [] - } -] as const; diff --git a/scripts/abi/optionRound.ts b/scripts/abi/optionRound.ts index 124d34c3..301ad38f 100644 --- a/scripts/abi/optionRound.ts +++ b/scripts/abi/optionRound.ts @@ -2,7 +2,7 @@ export const ABI = [ { "type": "impl", "name": "ERC20MetadataImpl", - "interface_name": "openzeppelin::token::erc20::interface::IERC20Metadata" + "interface_name": "openzeppelin_token::erc20::interface::IERC20Metadata" }, { "type": "struct", @@ -24,7 +24,7 @@ export const ABI = [ }, { "type": "interface", - "name": "openzeppelin::token::erc20::interface::IERC20Metadata", + "name": "openzeppelin_token::erc20::interface::IERC20Metadata", "items": [ { "type": "function", @@ -64,7 +64,7 @@ export const ABI = [ { "type": "impl", "name": "OptionRoundImpl", - "interface_name": "pitch_lake_starknet::option_round::interface::IOptionRound" + "interface_name": "pitch_lake::option_round::interface::IOptionRound" }, { "type": "struct", @@ -82,7 +82,7 @@ export const ABI = [ }, { "type": "enum", - "name": "pitch_lake_starknet::types::OptionRoundState", + "name": "pitch_lake::option_round::interface::OptionRoundState", "variants": [ { "name": "Open", @@ -104,7 +104,7 @@ export const ABI = [ }, { "type": "struct", - "name": "pitch_lake_starknet::types::Bid", + "name": "pitch_lake::types::Bid", "members": [ { "name": "bid_id", @@ -128,9 +128,35 @@ export const ABI = [ } ] }, + { + "type": "struct", + "name": "pitch_lake::vault::interface::PricingDataPoints", + "members": [ + { + "name": "twap", + "type": "core::integer::u256" + }, + { + "name": "volatility", + "type": "core::integer::u128" + }, + { + "name": "reserve_price", + "type": "core::integer::u256" + }, + { + "name": "cap_level", + "type": "core::integer::u128" + }, + { + "name": "strike_price", + "type": "core::integer::u256" + } + ] + }, { "type": "interface", - "name": "pitch_lake_starknet::option_round::interface::IOptionRound", + "name": "pitch_lake::option_round::interface::IOptionRound", "items": [ { "type": "function", @@ -160,7 +186,18 @@ export const ABI = [ "inputs": [], "outputs": [ { - "type": "pitch_lake_starknet::types::OptionRoundState" + "type": "pitch_lake::option_round::interface::OptionRoundState" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_deployment_date", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" } ], "state_mutability": "view" @@ -357,7 +394,7 @@ export const ABI = [ ], "outputs": [ { - "type": "pitch_lake_starknet::types::Bid" + "type": "pitch_lake::types::Bid" } ], "state_mutability": "view" @@ -373,7 +410,7 @@ export const ABI = [ ], "outputs": [ { - "type": "core::array::Array::" + "type": "core::array::Array::" } ], "state_mutability": "view" @@ -442,6 +479,22 @@ export const ABI = [ ], "state_mutability": "view" }, + { + "type": "function", + "name": "refresh_pricing_data_points", + "inputs": [ + { + "name": "pricing_data_points_now", + "type": "pitch_lake::vault::interface::PricingDataPoints" + }, + { + "name": "job_id", + "type": "core::felt252" + } + ], + "outputs": [], + "state_mutability": "external" + }, { "type": "function", "name": "start_auction", @@ -485,26 +538,6 @@ export const ABI = [ ], "state_mutability": "external" }, - { - "type": "function", - "name": "update_round_params", - "inputs": [ - { - "name": "reserve_price", - "type": "core::integer::u256" - }, - { - "name": "cap_level", - "type": "core::integer::u128" - }, - { - "name": "strike_price", - "type": "core::integer::u256" - } - ], - "outputs": [], - "state_mutability": "external" - }, { "type": "function", "name": "place_bid", @@ -520,7 +553,7 @@ export const ABI = [ ], "outputs": [ { - "type": "pitch_lake_starknet::types::Bid" + "type": "pitch_lake::types::Bid" } ], "state_mutability": "external" @@ -540,7 +573,7 @@ export const ABI = [ ], "outputs": [ { - "type": "pitch_lake_starknet::types::Bid" + "type": "pitch_lake::types::Bid" } ], "state_mutability": "external" @@ -588,7 +621,7 @@ export const ABI = [ { "type": "impl", "name": "ERC20Impl", - "interface_name": "openzeppelin::token::erc20::interface::IERC20" + "interface_name": "openzeppelin_token::erc20::interface::IERC20" }, { "type": "enum", @@ -606,7 +639,7 @@ export const ABI = [ }, { "type": "interface", - "name": "openzeppelin::token::erc20::interface::IERC20", + "name": "openzeppelin_token::erc20::interface::IERC20", "items": [ { "type": "function", @@ -724,11 +757,11 @@ export const ABI = [ { "type": "impl", "name": "ERC20CamelOnlyImpl", - "interface_name": "openzeppelin::token::erc20::interface::IERC20CamelOnly" + "interface_name": "openzeppelin_token::erc20::interface::IERC20CamelOnly" }, { "type": "interface", - "name": "openzeppelin::token::erc20::interface::IERC20CamelOnly", + "name": "openzeppelin_token::erc20::interface::IERC20CamelOnly", "items": [ { "type": "function", @@ -784,9 +817,9 @@ export const ABI = [ ] }, { - "type": "constructor", - "name": "constructor", - "inputs": [ + "type": "struct", + "name": "pitch_lake::option_round::interface::ConstructorArgs", + "members": [ { "name": "vault_address", "type": "core::starknet::contract_address::ContractAddress" @@ -808,22 +841,41 @@ export const ABI = [ "type": "core::integer::u64" }, { - "name": "reserve_price", - "type": "core::integer::u256" - }, + "name": "pricing_data_points", + "type": "pitch_lake::vault::interface::PricingDataPoints" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ { - "name": "cap_level", - "type": "core::integer::u128" + "name": "args", + "type": "pitch_lake::option_round::interface::ConstructorArgs" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::option_round::contract::OptionRound::PricingDataUpdated", + "kind": "struct", + "members": [ + { + "name": "pricing_data_points_now", + "type": "pitch_lake::vault::interface::PricingDataPoints", + "kind": "data" }, { - "name": "strike_price", - "type": "core::integer::u256" + "name": "job_id", + "type": "core::felt252", + "kind": "data" } ] }, { "type": "event", - "name": "pitch_lake_starknet::option_round::contract::OptionRound::AuctionStarted", + "name": "pitch_lake::option_round::contract::OptionRound::AuctionStarted", "kind": "struct", "members": [ { @@ -840,7 +892,7 @@ export const ABI = [ }, { "type": "event", - "name": "pitch_lake_starknet::option_round::contract::OptionRound::BidPlaced", + "name": "pitch_lake::option_round::contract::OptionRound::BidPlaced", "kind": "struct", "members": [ { @@ -872,7 +924,7 @@ export const ABI = [ }, { "type": "event", - "name": "pitch_lake_starknet::option_round::contract::OptionRound::BidUpdated", + "name": "pitch_lake::option_round::contract::OptionRound::BidUpdated", "kind": "struct", "members": [ { @@ -899,7 +951,7 @@ export const ABI = [ }, { "type": "event", - "name": "pitch_lake_starknet::option_round::contract::OptionRound::AuctionEnded", + "name": "pitch_lake::option_round::contract::OptionRound::AuctionEnded", "kind": "struct", "members": [ { @@ -921,7 +973,7 @@ export const ABI = [ }, { "type": "event", - "name": "pitch_lake_starknet::option_round::contract::OptionRound::OptionRoundSettled", + "name": "pitch_lake::option_round::contract::OptionRound::OptionRoundSettled", "kind": "struct", "members": [ { @@ -938,7 +990,7 @@ export const ABI = [ }, { "type": "event", - "name": "pitch_lake_starknet::option_round::contract::OptionRound::OptionsExercised", + "name": "pitch_lake::option_round::contract::OptionRound::OptionsExercised", "kind": "struct", "members": [ { @@ -960,7 +1012,7 @@ export const ABI = [ }, { "type": "event", - "name": "pitch_lake_starknet::option_round::contract::OptionRound::UnusedBidsRefunded", + "name": "pitch_lake::option_round::contract::OptionRound::UnusedBidsRefunded", "kind": "struct", "members": [ { @@ -977,11 +1029,11 @@ export const ABI = [ }, { "type": "struct", - "name": "pitch_lake_starknet::library::red_black_tree::RBTreeComponent::Node", + "name": "pitch_lake::library::red_black_tree::RBTreeComponent::Node", "members": [ { "name": "value", - "type": "pitch_lake_starknet::types::Bid" + "type": "pitch_lake::types::Bid" }, { "name": "left", @@ -1003,31 +1055,31 @@ export const ABI = [ }, { "type": "event", - "name": "pitch_lake_starknet::library::red_black_tree::RBTreeComponent::InsertEvent", + "name": "pitch_lake::library::red_black_tree::RBTreeComponent::InsertEvent", "kind": "struct", "members": [ { "name": "node", - "type": "pitch_lake_starknet::library::red_black_tree::RBTreeComponent::Node", + "type": "pitch_lake::library::red_black_tree::RBTreeComponent::Node", "kind": "data" } ] }, { "type": "event", - "name": "pitch_lake_starknet::library::red_black_tree::RBTreeComponent::Event", + "name": "pitch_lake::library::red_black_tree::RBTreeComponent::Event", "kind": "enum", "variants": [ { "name": "InsertEvent", - "type": "pitch_lake_starknet::library::red_black_tree::RBTreeComponent::InsertEvent", + "type": "pitch_lake::library::red_black_tree::RBTreeComponent::InsertEvent", "kind": "nested" } ] }, { "type": "event", - "name": "pitch_lake_starknet::option_round::contract::OptionRound::OptionsMinted", + "name": "pitch_lake::option_round::contract::OptionRound::OptionsMinted", "kind": "struct", "members": [ { @@ -1044,7 +1096,7 @@ export const ABI = [ }, { "type": "event", - "name": "openzeppelin::token::erc20::erc20::ERC20Component::Transfer", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Transfer", "kind": "struct", "members": [ { @@ -1066,7 +1118,7 @@ export const ABI = [ }, { "type": "event", - "name": "openzeppelin::token::erc20::erc20::ERC20Component::Approval", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Approval", "kind": "struct", "members": [ { @@ -1088,74 +1140,79 @@ export const ABI = [ }, { "type": "event", - "name": "openzeppelin::token::erc20::erc20::ERC20Component::Event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Event", "kind": "enum", "variants": [ { "name": "Transfer", - "type": "openzeppelin::token::erc20::erc20::ERC20Component::Transfer", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Transfer", "kind": "nested" }, { "name": "Approval", - "type": "openzeppelin::token::erc20::erc20::ERC20Component::Approval", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Approval", "kind": "nested" } ] }, { "type": "event", - "name": "pitch_lake_starknet::option_round::contract::OptionRound::Event", + "name": "pitch_lake::option_round::contract::OptionRound::Event", "kind": "enum", "variants": [ + { + "name": "PricingDataUpdated", + "type": "pitch_lake::option_round::contract::OptionRound::PricingDataUpdated", + "kind": "nested" + }, { "name": "AuctionStarted", - "type": "pitch_lake_starknet::option_round::contract::OptionRound::AuctionStarted", + "type": "pitch_lake::option_round::contract::OptionRound::AuctionStarted", "kind": "nested" }, { "name": "BidPlaced", - "type": "pitch_lake_starknet::option_round::contract::OptionRound::BidPlaced", + "type": "pitch_lake::option_round::contract::OptionRound::BidPlaced", "kind": "nested" }, { "name": "BidUpdated", - "type": "pitch_lake_starknet::option_round::contract::OptionRound::BidUpdated", + "type": "pitch_lake::option_round::contract::OptionRound::BidUpdated", "kind": "nested" }, { "name": "AuctionEnded", - "type": "pitch_lake_starknet::option_round::contract::OptionRound::AuctionEnded", + "type": "pitch_lake::option_round::contract::OptionRound::AuctionEnded", "kind": "nested" }, { "name": "OptionRoundSettled", - "type": "pitch_lake_starknet::option_round::contract::OptionRound::OptionRoundSettled", + "type": "pitch_lake::option_round::contract::OptionRound::OptionRoundSettled", "kind": "nested" }, { "name": "OptionsExercised", - "type": "pitch_lake_starknet::option_round::contract::OptionRound::OptionsExercised", + "type": "pitch_lake::option_round::contract::OptionRound::OptionsExercised", "kind": "nested" }, { "name": "UnusedBidsRefunded", - "type": "pitch_lake_starknet::option_round::contract::OptionRound::UnusedBidsRefunded", + "type": "pitch_lake::option_round::contract::OptionRound::UnusedBidsRefunded", "kind": "nested" }, { "name": "BidTreeEvent", - "type": "pitch_lake_starknet::library::red_black_tree::RBTreeComponent::Event", + "type": "pitch_lake::library::red_black_tree::RBTreeComponent::Event", "kind": "flat" }, { "name": "OptionsMinted", - "type": "pitch_lake_starknet::option_round::contract::OptionRound::OptionsMinted", + "type": "pitch_lake::option_round::contract::OptionRound::OptionsMinted", "kind": "nested" }, { "name": "ERC20Event", - "type": "openzeppelin::token::erc20::erc20::ERC20Component::Event", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Event", "kind": "flat" } ] diff --git a/scripts/abi/vault.ts b/scripts/abi/vault.ts index b2a9f5e2..e48ffdd7 100644 --- a/scripts/abi/vault.ts +++ b/scripts/abi/vault.ts @@ -2,11 +2,11 @@ export const ABI = [ { "type": "impl", "name": "VaultImpl", - "interface_name": "pitch_lake_starknet::vault::interface::IVault" + "interface_name": "pitch_lake::vault::interface::IVault" }, { "type": "enum", - "name": "pitch_lake_starknet::types::VaultType", + "name": "pitch_lake::vault::interface::VaultType", "variants": [ { "name": "InTheMoney", @@ -36,9 +36,51 @@ export const ABI = [ } ] }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "pitch_lake::fact_registry::interface::JobRequestParams", + "members": [ + { + "name": "twap", + "type": "(core::integer::u64, core::integer::u64)" + }, + { + "name": "volatility", + "type": "(core::integer::u64, core::integer::u64)" + }, + { + "name": "reserve_price", + "type": "(core::integer::u64, core::integer::u64)" + } + ] + }, + { + "type": "struct", + "name": "pitch_lake::fact_registry::interface::JobRequest", + "members": [ + { + "name": "identifiers", + "type": "core::array::Span::" + }, + { + "name": "params", + "type": "pitch_lake::fact_registry::interface::JobRequestParams" + } + ] + }, { "type": "interface", - "name": "pitch_lake_starknet::vault::interface::IVault", + "name": "pitch_lake::vault::interface::IVault", "items": [ { "type": "function", @@ -46,14 +88,14 @@ export const ABI = [ "inputs": [], "outputs": [ { - "type": "pitch_lake_starknet::types::VaultType" + "type": "pitch_lake::vault::interface::VaultType" } ], "state_mutability": "view" }, { "type": "function", - "name": "get_market_aggregator_address", + "name": "get_fact_registry_address", "inputs": [], "outputs": [ { @@ -334,8 +376,13 @@ export const ABI = [ }, { "type": "function", - "name": "update_round_params", - "inputs": [], + "name": "refresh_round_pricing_data", + "inputs": [ + { + "name": "job_request", + "type": "pitch_lake::fact_registry::interface::JobRequest" + } + ], "outputs": [], "state_mutability": "external" }, @@ -364,7 +411,12 @@ export const ABI = [ { "type": "function", "name": "settle_round", - "inputs": [], + "inputs": [ + { + "name": "job_request", + "type": "pitch_lake::fact_registry::interface::JobRequest" + } + ], "outputs": [ { "type": "core::integer::u256" @@ -375,9 +427,9 @@ export const ABI = [ ] }, { - "type": "constructor", - "name": "constructor", - "inputs": [ + "type": "struct", + "name": "pitch_lake::vault::interface::ConstructorArgs", + "members": [ { "name": "round_transition_period", "type": "core::integer::u64" @@ -396,10 +448,10 @@ export const ABI = [ }, { "name": "vault_type", - "type": "pitch_lake_starknet::types::VaultType" + "type": "pitch_lake::vault::interface::VaultType" }, { - "name": "market_aggregator_address", + "name": "fact_registry_address", "type": "core::starknet::contract_address::ContractAddress" }, { @@ -408,9 +460,19 @@ export const ABI = [ } ] }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "args", + "type": "pitch_lake::vault::interface::ConstructorArgs" + } + ] + }, { "type": "event", - "name": "pitch_lake_starknet::vault::contract::Vault::Deposit", + "name": "pitch_lake::vault::contract::Vault::Deposit", "kind": "struct", "members": [ { @@ -437,7 +499,7 @@ export const ABI = [ }, { "type": "event", - "name": "pitch_lake_starknet::vault::contract::Vault::Withdrawal", + "name": "pitch_lake::vault::contract::Vault::Withdrawal", "kind": "struct", "members": [ { @@ -464,7 +526,7 @@ export const ABI = [ }, { "type": "event", - "name": "pitch_lake_starknet::vault::contract::Vault::WithdrawalQueued", + "name": "pitch_lake::vault::contract::Vault::WithdrawalQueued", "kind": "struct", "members": [ { @@ -491,7 +553,7 @@ export const ABI = [ }, { "type": "event", - "name": "pitch_lake_starknet::vault::contract::Vault::StashWithdrawn", + "name": "pitch_lake::vault::contract::Vault::StashWithdrawn", "kind": "struct", "members": [ { @@ -513,7 +575,7 @@ export const ABI = [ }, { "type": "event", - "name": "pitch_lake_starknet::vault::contract::Vault::OptionRoundDeployed", + "name": "pitch_lake::vault::contract::Vault::OptionRoundDeployed", "kind": "struct", "members": [ { @@ -560,32 +622,32 @@ export const ABI = [ }, { "type": "event", - "name": "pitch_lake_starknet::vault::contract::Vault::Event", + "name": "pitch_lake::vault::contract::Vault::Event", "kind": "enum", "variants": [ { "name": "Deposit", - "type": "pitch_lake_starknet::vault::contract::Vault::Deposit", + "type": "pitch_lake::vault::contract::Vault::Deposit", "kind": "nested" }, { "name": "Withdrawal", - "type": "pitch_lake_starknet::vault::contract::Vault::Withdrawal", + "type": "pitch_lake::vault::contract::Vault::Withdrawal", "kind": "nested" }, { "name": "WithdrawalQueued", - "type": "pitch_lake_starknet::vault::contract::Vault::WithdrawalQueued", + "type": "pitch_lake::vault::contract::Vault::WithdrawalQueued", "kind": "nested" }, { "name": "StashWithdrawn", - "type": "pitch_lake_starknet::vault::contract::Vault::StashWithdrawn", + "type": "pitch_lake::vault::contract::Vault::StashWithdrawn", "kind": "nested" }, { "name": "OptionRoundDeployed", - "type": "pitch_lake_starknet::vault::contract::Vault::OptionRoundDeployed", + "type": "pitch_lake::vault::contract::Vault::OptionRoundDeployed", "kind": "nested" } ] diff --git a/scripts/dist/scripts/abi/erc20.js b/scripts/dist/scripts/abi/erc20.js new file mode 100644 index 00000000..f09d815b --- /dev/null +++ b/scripts/dist/scripts/abi/erc20.js @@ -0,0 +1,341 @@ +export const ABI = [ + { + "type": "impl", + "name": "ERC20MixinImpl", + "interface_name": "openzeppelin::token::erc20::interface::ERC20ABI" + }, + { + "type": "struct", + "name": "core::integer::u256", + "members": [ + { + "name": "low", + "type": "core::integer::u128" + }, + { + "name": "high", + "type": "core::integer::u128" + } + ] + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "interface", + "name": "openzeppelin::token::erc20::interface::ERC20ABI", + "items": [ + { + "type": "function", + "name": "total_supply", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "balance_of", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "allowance", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "transfer", + "inputs": [ + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "transfer_from", + "inputs": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "approve", + "inputs": [ + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "name", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "symbol", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "decimals", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u8" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "totalSupply", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "balanceOf", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "transferFrom", + "inputs": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "initial_supply", + "type": "core::integer::u256" + }, + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "event", + "name": "openzeppelin::token::erc20::erc20::ERC20Component::Transfer", + "kind": "struct", + "members": [ + { + "name": "from", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "to", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin::token::erc20::erc20::ERC20Component::Approval", + "kind": "struct", + "members": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin::token::erc20::erc20::ERC20Component::Event", + "kind": "enum", + "variants": [ + { + "name": "Transfer", + "type": "openzeppelin::token::erc20::erc20::ERC20Component::Transfer", + "kind": "nested" + }, + { + "name": "Approval", + "type": "openzeppelin::token::erc20::erc20::ERC20Component::Approval", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "pitch_lake_starknet::library::eth::Eth::Event", + "kind": "enum", + "variants": [ + { + "name": "ERC20Event", + "type": "openzeppelin::token::erc20::erc20::ERC20Component::Event", + "kind": "flat" + } + ] + } +]; diff --git a/scripts/dist/scripts/abi/eth.js b/scripts/dist/scripts/abi/eth.js new file mode 100644 index 00000000..45928994 --- /dev/null +++ b/scripts/dist/scripts/abi/eth.js @@ -0,0 +1,341 @@ +export const ABI = [ + { + "type": "impl", + "name": "ERC20MixinImpl", + "interface_name": "openzeppelin_token::erc20::interface::ERC20ABI" + }, + { + "type": "struct", + "name": "core::integer::u256", + "members": [ + { + "name": "low", + "type": "core::integer::u128" + }, + { + "name": "high", + "type": "core::integer::u128" + } + ] + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "interface", + "name": "openzeppelin_token::erc20::interface::ERC20ABI", + "items": [ + { + "type": "function", + "name": "total_supply", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "balance_of", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "allowance", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "transfer", + "inputs": [ + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "transfer_from", + "inputs": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "approve", + "inputs": [ + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "name", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "symbol", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "decimals", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u8" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "totalSupply", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "balanceOf", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "transferFrom", + "inputs": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "initial_supply", + "type": "core::integer::u256" + }, + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Transfer", + "kind": "struct", + "members": [ + { + "name": "from", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "to", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Approval", + "kind": "struct", + "members": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Event", + "kind": "enum", + "variants": [ + { + "name": "Transfer", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Transfer", + "kind": "nested" + }, + { + "name": "Approval", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Approval", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::library::eth::Eth::Event", + "kind": "enum", + "variants": [ + { + "name": "ERC20Event", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Event", + "kind": "flat" + } + ] + } +]; diff --git a/scripts/dist/scripts/abi/factRegistry.js b/scripts/dist/scripts/abi/factRegistry.js new file mode 100644 index 00000000..f5fe714b --- /dev/null +++ b/scripts/dist/scripts/abi/factRegistry.js @@ -0,0 +1,102 @@ +export const ABI = [ + { + "type": "impl", + "name": "FactRegistryImpl", + "interface_name": "pitch_lake::fact_registry::interface::IFactRegistry" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "pitch_lake::fact_registry::interface::JobRequestParams", + "members": [ + { + "name": "twap", + "type": "(core::integer::u64, core::integer::u64)" + }, + { + "name": "volatility", + "type": "(core::integer::u64, core::integer::u64)" + }, + { + "name": "reserve_price", + "type": "(core::integer::u64, core::integer::u64)" + } + ] + }, + { + "type": "struct", + "name": "pitch_lake::fact_registry::interface::JobRequest", + "members": [ + { + "name": "identifiers", + "type": "core::array::Span::" + }, + { + "name": "params", + "type": "pitch_lake::fact_registry::interface::JobRequestParams" + } + ] + }, + { + "type": "interface", + "name": "pitch_lake::fact_registry::interface::IFactRegistry", + "items": [ + { + "type": "function", + "name": "get_fact", + "inputs": [ + { + "name": "job_id", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_fact", + "inputs": [ + { + "name": "job_request", + "type": "pitch_lake::fact_registry::interface::JobRequest" + }, + { + "name": "job_data", + "type": "core::array::Span::" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [] + }, + { + "type": "event", + "name": "pitch_lake::fact_registry::contract::FactRegistry::Event", + "kind": "enum", + "variants": [] + } +]; diff --git a/scripts/dist/scripts/abi/index.js b/scripts/dist/scripts/abi/index.js new file mode 100644 index 00000000..d720d0e8 --- /dev/null +++ b/scripts/dist/scripts/abi/index.js @@ -0,0 +1,4 @@ +export { ABI as vaultABI } from "./vault"; +export { ABI as erc20ABI } from "./erc20"; +export { ABI as optionRoundABI } from "./optionRound"; +export { ABI as factRegistryABI } from "./factRegistry"; diff --git a/scripts/dist/scripts/abi/optionRound.js b/scripts/dist/scripts/abi/optionRound.js new file mode 100644 index 00000000..a67413b0 --- /dev/null +++ b/scripts/dist/scripts/abi/optionRound.js @@ -0,0 +1,1220 @@ +export const ABI = [ + { + "type": "impl", + "name": "ERC20MetadataImpl", + "interface_name": "openzeppelin_token::erc20::interface::IERC20Metadata" + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "interface", + "name": "openzeppelin_token::erc20::interface::IERC20Metadata", + "items": [ + { + "type": "function", + "name": "name", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "symbol", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "decimals", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u8" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "OptionRoundImpl", + "interface_name": "pitch_lake::option_round::interface::IOptionRound" + }, + { + "type": "struct", + "name": "core::integer::u256", + "members": [ + { + "name": "low", + "type": "core::integer::u128" + }, + { + "name": "high", + "type": "core::integer::u128" + } + ] + }, + { + "type": "enum", + "name": "pitch_lake::option_round::interface::OptionRoundState", + "variants": [ + { + "name": "Open", + "type": "()" + }, + { + "name": "Auctioning", + "type": "()" + }, + { + "name": "Running", + "type": "()" + }, + { + "name": "Settled", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "pitch_lake::types::Bid", + "members": [ + { + "name": "bid_id", + "type": "core::felt252" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + }, + { + "name": "price", + "type": "core::integer::u256" + }, + { + "name": "tree_nonce", + "type": "core::integer::u64" + } + ] + }, + { + "type": "struct", + "name": "pitch_lake::vault::interface::PricingDataPoints", + "members": [ + { + "name": "twap", + "type": "core::integer::u256" + }, + { + "name": "volatility", + "type": "core::integer::u128" + }, + { + "name": "reserve_price", + "type": "core::integer::u256" + }, + { + "name": "cap_level", + "type": "core::integer::u128" + }, + { + "name": "strike_price", + "type": "core::integer::u256" + } + ] + }, + { + "type": "interface", + "name": "pitch_lake::option_round::interface::IOptionRound", + "items": [ + { + "type": "function", + "name": "get_vault_address", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_round_id", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_state", + "inputs": [], + "outputs": [ + { + "type": "pitch_lake::option_round::interface::OptionRoundState" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_deployment_date", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_auction_start_date", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_auction_end_date", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_option_settlement_date", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_starting_liquidity", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_unsold_liquidity", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_reserve_price", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_strike_price", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_cap_level", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u128" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_options_available", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_options_sold", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_clearing_price", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_total_premium", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_settlement_price", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_total_payout", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_account_bid_nonce", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_bid_tree_nonce", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_bid_details", + "inputs": [ + { + "name": "bid_id", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "pitch_lake::types::Bid" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_account_bids", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::array::Array::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_account_refundable_balance", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_account_mintable_options", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_account_total_options", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_account_payout_balance", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "refresh_pricing_data_points", + "inputs": [ + { + "name": "pricing_data_points_now", + "type": "pitch_lake::vault::interface::PricingDataPoints" + }, + { + "name": "job_id", + "type": "core::felt252" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "start_auction", + "inputs": [ + { + "name": "starting_liquidity", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "end_auction", + "inputs": [], + "outputs": [ + { + "type": "(core::integer::u256, core::integer::u256)" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "settle_round", + "inputs": [ + { + "name": "settlement_price", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "place_bid", + "inputs": [ + { + "name": "amount", + "type": "core::integer::u256" + }, + { + "name": "price", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "pitch_lake::types::Bid" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "update_bid", + "inputs": [ + { + "name": "bid_id", + "type": "core::felt252" + }, + { + "name": "price_increase", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "pitch_lake::types::Bid" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "refund_unused_bids", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "exercise_options", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "mint_options", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "ERC20Impl", + "interface_name": "openzeppelin_token::erc20::interface::IERC20" + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "interface", + "name": "openzeppelin_token::erc20::interface::IERC20", + "items": [ + { + "type": "function", + "name": "total_supply", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "balance_of", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "allowance", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "transfer", + "inputs": [ + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "transfer_from", + "inputs": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "approve", + "inputs": [ + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "ERC20CamelOnlyImpl", + "interface_name": "openzeppelin_token::erc20::interface::IERC20CamelOnly" + }, + { + "type": "interface", + "name": "openzeppelin_token::erc20::interface::IERC20CamelOnly", + "items": [ + { + "type": "function", + "name": "totalSupply", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "balanceOf", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "transferFrom", + "inputs": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "struct", + "name": "pitch_lake::option_round::interface::ConstructorArgs", + "members": [ + { + "name": "vault_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "round_id", + "type": "core::integer::u256" + }, + { + "name": "auction_start_date", + "type": "core::integer::u64" + }, + { + "name": "auction_end_date", + "type": "core::integer::u64" + }, + { + "name": "option_settlement_date", + "type": "core::integer::u64" + }, + { + "name": "pricing_data_points", + "type": "pitch_lake::vault::interface::PricingDataPoints" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "args", + "type": "pitch_lake::option_round::interface::ConstructorArgs" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::option_round::contract::OptionRound::PricingDataUpdated", + "kind": "struct", + "members": [ + { + "name": "pricing_data_points_now", + "type": "pitch_lake::vault::interface::PricingDataPoints", + "kind": "data" + }, + { + "name": "job_id", + "type": "core::felt252", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::option_round::contract::OptionRound::AuctionStarted", + "kind": "struct", + "members": [ + { + "name": "starting_liquidity", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "options_available", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::option_round::contract::OptionRound::BidPlaced", + "kind": "struct", + "members": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "bid_id", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "amount", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "price", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "bid_tree_nonce_now", + "type": "core::integer::u64", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::option_round::contract::OptionRound::BidUpdated", + "kind": "struct", + "members": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "bid_id", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "price_increase", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "bid_tree_nonce_now", + "type": "core::integer::u64", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::option_round::contract::OptionRound::AuctionEnded", + "kind": "struct", + "members": [ + { + "name": "options_sold", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "clearing_price", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "unsold_liquidity", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::option_round::contract::OptionRound::OptionRoundSettled", + "kind": "struct", + "members": [ + { + "name": "settlement_price", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "payout_per_option", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::option_round::contract::OptionRound::OptionsExercised", + "kind": "struct", + "members": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "number_of_options", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "exercised_amount", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::option_round::contract::OptionRound::UnusedBidsRefunded", + "kind": "struct", + "members": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "refunded_amount", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "struct", + "name": "pitch_lake::library::red_black_tree::RBTreeComponent::Node", + "members": [ + { + "name": "value", + "type": "pitch_lake::types::Bid" + }, + { + "name": "left", + "type": "core::felt252" + }, + { + "name": "right", + "type": "core::felt252" + }, + { + "name": "parent", + "type": "core::felt252" + }, + { + "name": "color", + "type": "core::bool" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::library::red_black_tree::RBTreeComponent::InsertEvent", + "kind": "struct", + "members": [ + { + "name": "node", + "type": "pitch_lake::library::red_black_tree::RBTreeComponent::Node", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::library::red_black_tree::RBTreeComponent::Event", + "kind": "enum", + "variants": [ + { + "name": "InsertEvent", + "type": "pitch_lake::library::red_black_tree::RBTreeComponent::InsertEvent", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::option_round::contract::OptionRound::OptionsMinted", + "kind": "struct", + "members": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "minted_amount", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Transfer", + "kind": "struct", + "members": [ + { + "name": "from", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "to", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Approval", + "kind": "struct", + "members": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Event", + "kind": "enum", + "variants": [ + { + "name": "Transfer", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Transfer", + "kind": "nested" + }, + { + "name": "Approval", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Approval", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::option_round::contract::OptionRound::Event", + "kind": "enum", + "variants": [ + { + "name": "PricingDataUpdated", + "type": "pitch_lake::option_round::contract::OptionRound::PricingDataUpdated", + "kind": "nested" + }, + { + "name": "AuctionStarted", + "type": "pitch_lake::option_round::contract::OptionRound::AuctionStarted", + "kind": "nested" + }, + { + "name": "BidPlaced", + "type": "pitch_lake::option_round::contract::OptionRound::BidPlaced", + "kind": "nested" + }, + { + "name": "BidUpdated", + "type": "pitch_lake::option_round::contract::OptionRound::BidUpdated", + "kind": "nested" + }, + { + "name": "AuctionEnded", + "type": "pitch_lake::option_round::contract::OptionRound::AuctionEnded", + "kind": "nested" + }, + { + "name": "OptionRoundSettled", + "type": "pitch_lake::option_round::contract::OptionRound::OptionRoundSettled", + "kind": "nested" + }, + { + "name": "OptionsExercised", + "type": "pitch_lake::option_round::contract::OptionRound::OptionsExercised", + "kind": "nested" + }, + { + "name": "UnusedBidsRefunded", + "type": "pitch_lake::option_round::contract::OptionRound::UnusedBidsRefunded", + "kind": "nested" + }, + { + "name": "BidTreeEvent", + "type": "pitch_lake::library::red_black_tree::RBTreeComponent::Event", + "kind": "flat" + }, + { + "name": "OptionsMinted", + "type": "pitch_lake::option_round::contract::OptionRound::OptionsMinted", + "kind": "nested" + }, + { + "name": "ERC20Event", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Event", + "kind": "flat" + } + ] + } +]; diff --git a/scripts/dist/scripts/abi/vault.js b/scripts/dist/scripts/abi/vault.js new file mode 100644 index 00000000..928907cf --- /dev/null +++ b/scripts/dist/scripts/abi/vault.js @@ -0,0 +1,655 @@ +export const ABI = [ + { + "type": "impl", + "name": "VaultImpl", + "interface_name": "pitch_lake::vault::interface::IVault" + }, + { + "type": "enum", + "name": "pitch_lake::vault::interface::VaultType", + "variants": [ + { + "name": "InTheMoney", + "type": "()" + }, + { + "name": "AtTheMoney", + "type": "()" + }, + { + "name": "OutOfMoney", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "core::integer::u256", + "members": [ + { + "name": "low", + "type": "core::integer::u128" + }, + { + "name": "high", + "type": "core::integer::u128" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "pitch_lake::fact_registry::interface::JobRequestParams", + "members": [ + { + "name": "twap", + "type": "(core::integer::u64, core::integer::u64)" + }, + { + "name": "volatility", + "type": "(core::integer::u64, core::integer::u64)" + }, + { + "name": "reserve_price", + "type": "(core::integer::u64, core::integer::u64)" + } + ] + }, + { + "type": "struct", + "name": "pitch_lake::fact_registry::interface::JobRequest", + "members": [ + { + "name": "identifiers", + "type": "core::array::Span::" + }, + { + "name": "params", + "type": "pitch_lake::fact_registry::interface::JobRequestParams" + } + ] + }, + { + "type": "interface", + "name": "pitch_lake::vault::interface::IVault", + "items": [ + { + "type": "function", + "name": "get_vault_type", + "inputs": [], + "outputs": [ + { + "type": "pitch_lake::vault::interface::VaultType" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_fact_registry_address", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_eth_address", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_auction_run_time", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_option_run_time", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_round_transition_period", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_current_round_id", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_round_address", + "inputs": [ + { + "name": "option_round_id", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_vault_total_balance", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_vault_locked_balance", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_vault_unlocked_balance", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_vault_stashed_balance", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_vault_queued_bps", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u16" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_account_total_balance", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_account_locked_balance", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_account_unlocked_balance", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_account_stashed_balance", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_account_queued_bps", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u16" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "deposit", + "inputs": [ + { + "name": "amount", + "type": "core::integer::u256" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "queue_withdrawal", + "inputs": [ + { + "name": "bps", + "type": "core::integer::u16" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "withdraw_stash", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "refresh_round_pricing_data", + "inputs": [ + { + "name": "job_request", + "type": "pitch_lake::fact_registry::interface::JobRequest" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "start_auction", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "end_auction", + "inputs": [], + "outputs": [ + { + "type": "(core::integer::u256, core::integer::u256)" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "settle_round", + "inputs": [ + { + "name": "job_request", + "type": "pitch_lake::fact_registry::interface::JobRequest" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "struct", + "name": "pitch_lake::vault::interface::ConstructorArgs", + "members": [ + { + "name": "round_transition_period", + "type": "core::integer::u64" + }, + { + "name": "auction_run_time", + "type": "core::integer::u64" + }, + { + "name": "option_run_time", + "type": "core::integer::u64" + }, + { + "name": "eth_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "vault_type", + "type": "pitch_lake::vault::interface::VaultType" + }, + { + "name": "fact_registry_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "option_round_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "args", + "type": "pitch_lake::vault::interface::ConstructorArgs" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::vault::contract::Vault::Deposit", + "kind": "struct", + "members": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "amount", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "account_unlocked_balance_now", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "vault_unlocked_balance_now", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::vault::contract::Vault::Withdrawal", + "kind": "struct", + "members": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "amount", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "account_unlocked_balance_now", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "vault_unlocked_balance_now", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::vault::contract::Vault::WithdrawalQueued", + "kind": "struct", + "members": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "bps", + "type": "core::integer::u16", + "kind": "data" + }, + { + "name": "account_queued_liquidity_now", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "vault_queued_liquidity_now", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::vault::contract::Vault::StashWithdrawn", + "kind": "struct", + "members": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "amount", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "vault_stashed_balance_now", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::vault::contract::Vault::OptionRoundDeployed", + "kind": "struct", + "members": [ + { + "name": "round_id", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "reserve_price", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "strike_price", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "cap_level", + "type": "core::integer::u128", + "kind": "data" + }, + { + "name": "auction_start_date", + "type": "core::integer::u64", + "kind": "data" + }, + { + "name": "auction_end_date", + "type": "core::integer::u64", + "kind": "data" + }, + { + "name": "option_settlement_date", + "type": "core::integer::u64", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "pitch_lake::vault::contract::Vault::Event", + "kind": "enum", + "variants": [ + { + "name": "Deposit", + "type": "pitch_lake::vault::contract::Vault::Deposit", + "kind": "nested" + }, + { + "name": "Withdrawal", + "type": "pitch_lake::vault::contract::Vault::Withdrawal", + "kind": "nested" + }, + { + "name": "WithdrawalQueued", + "type": "pitch_lake::vault::contract::Vault::WithdrawalQueued", + "kind": "nested" + }, + { + "name": "StashWithdrawn", + "type": "pitch_lake::vault::contract::Vault::StashWithdrawn", + "kind": "nested" + }, + { + "name": "OptionRoundDeployed", + "type": "pitch_lake::vault::contract::Vault::OptionRoundDeployed", + "kind": "nested" + } + ] + } +]; diff --git a/scripts/dist/scripts/integrationTests/smokeTest1/auctionEnd.js b/scripts/dist/scripts/integrationTests/smokeTest1/auctionEnd.js new file mode 100644 index 00000000..7b0077a4 --- /dev/null +++ b/scripts/dist/scripts/integrationTests/smokeTest1/auctionEnd.js @@ -0,0 +1,126 @@ +import { getAccount } from "../../utils/helpers/common"; +import { getOptionRoundFacade } from "../../utils/helpers/setup"; +import assert from "assert"; +export const smokeTest = async ({ provider, vaultFacade, constants, getLPLockedBalanceAll, getLPUnlockedBalanceAll, endAuctionBystander, getLiquidityProviderAccounts, }) => { + const optionRoundFacade = await getOptionRoundFacade(provider, vaultFacade.vaultContract); + const devAccount = getAccount("dev", provider); + try { + await vaultFacade.endAuction(devAccount); + throw Error("Should have reverted"); + } + catch (err) { + const error = err; + assert(error.message !== "Should have reverted", error.message); + //Failure expected when contracts are changed to revert + } + const state = await optionRoundFacade.optionRoundContract.get_state(); + assert(state.activeVariant() === "Auctioning", `Expected:Auctioning\nReceived:${state.activeVariant()}`); + const liquidityProviderAccounts = getLiquidityProviderAccounts(2); + await endAuctionBystander(); + const lpUnlockedBalances = await getLPUnlockedBalanceAll(liquidityProviderAccounts); + const lpLockedBalances = await getLPLockedBalanceAll(liquidityProviderAccounts); + const totalPremiums = await optionRoundFacade.getTotalPremiums(); + const totalLocked = await vaultFacade.getTotalLocked(); + const totalUnlocked = await vaultFacade.getTotalUnLocked(); + checkpoint1({ + lpLockedBalances, + lpUnlockedBalances, + totalPremiums, + totalLocked, + totalUnlocked, + constants, + }); + const stateAfter = await optionRoundFacade.optionRoundContract.get_state(); + assert(stateAfter.activeVariant() === "Running", `Expected:Running\nReceived:${stateAfter.activeVariant()}`); + // const unlockedBalanceA = await vaultFacade.getLPUnlockedBalance( + // liquidityProviderA.address + // ); + // const unlockedBalanceB = await vaultFacade.getLPUnlockedBalance( + // liquidityProviderB.address + // ); + // const lockedBalanceA = await vaultFacade.getLPLockedBalance( + // liquidityProviderA.address + // ); + // const lockedBalanceB = await vaultFacade.getLPLockedBalance( + // liquidityProviderB.address + // ); + // const totalLockedAmount = await vaultFacade.getTotalLocked(); + // const totalUnlockedAmount = await vaultFacade.getTotalUnLocked(); + // //Asserts + // checkpoint1({ + // unlockedBalanceA, + // unlockedBalanceB, + // lockedBalanceA, + // lockedBalanceB, + // totalLockedAmount, + // totalUnlockedAmount, + // constants, + // }); + // //Approve OptionBidders + // const approveAllData: Array = [ + // { + // owner: optionBidderA, + // amount: BigInt("90000000000000000000"), + // spender: optionRoundFacade.optionRoundContract.address, + // }, + // { + // owner: optionBidderB, + // amount: BigInt("90000000000000000000"), + // spender: optionRoundFacade.optionRoundContract.address, + // }, + // ]; + // await ethFacade.approveAll(approveAllData); + // await mineNextBlock(provider.channel.nodeUrl); + // //Place bids according to story script + // const reservePrice = await optionRoundFacade.getReservePrice(); + // const totalOptionAvailable = + // await optionRoundFacade.getTotalOptionsAvailable(); + // const balanceBeforeBidA = await ethFacade.getBalance(optionBidderA.address); + // const balanceBeforeBidB = await ethFacade.getBalance(optionBidderB.address); + // console.log( + // "reservePrice", + // Number(reservePrice), + // "\ntotalOptionsAvailable:", + // totalOptionAvailable + // ); + // const placeBidsData: Array = [ + // { + // from: optionBidderA, + // amount: BigInt(totalOptionAvailable) / BigInt(2), + // price: BigInt(3) * BigInt(reservePrice), + // }, + // { + // from: optionBidderB, + // amount: BigInt(totalOptionAvailable) / BigInt(2), + // price: BigInt(2) * BigInt(reservePrice), + // }, + // { + // from: optionBidderB, + // amount: BigInt(totalOptionAvailable) / BigInt(2), + // price: BigInt(reservePrice), + // }, + // ]; + // await optionRoundFacade.placeBidsAll(placeBidsData); + // const balanceAfterBidA = await ethFacade.getBalance(optionBidderA.address); + // const balanceAfterBidB = await ethFacade.getBalance(optionBidderB.address); + // const bidsForA = await optionRoundFacade.getBidsFor(optionBidderA.address); + // const bidsForB = await optionRoundFacade.getBidsFor(optionBidderB.address); + // checkpoint2({ + // balanceBeforeBidA, + // balanceAfterBidA, + // balanceBeforeBidB, + // balanceAfterBidB, + // reservePrice, + // totalOptionAvailable, + // bidsForA, + // bidsForB, + // }); +}; +async function checkpoint1({ lpLockedBalances, lpUnlockedBalances, totalPremiums, totalLocked, totalUnlocked, constants, }) { + assert(BigInt(lpUnlockedBalances[0]) === BigInt(totalPremiums) / BigInt(2), "LP Unlocked for A mismatch"); + assert(BigInt(lpUnlockedBalances[1]) === BigInt(totalPremiums) / BigInt(2), "LP Unlocked for B mismatch"); + assert(BigInt(lpLockedBalances[0]) === BigInt(constants.depositAmount) / BigInt(2), "LP Locked for A mismatch"); + assert(BigInt(lpLockedBalances[1]) === BigInt(constants.depositAmount) / BigInt(2), "LP Locked for B mismatch"); + assert(BigInt(totalLocked) === BigInt(constants.depositAmount), "totalLocked mismatch"); + assert(BigInt(totalUnlocked) === BigInt(totalPremiums), "totalUnlocked for A mismatch"); +} diff --git a/scripts/dist/scripts/integrationTests/smokeTest1/auctionOpen.js b/scripts/dist/scripts/integrationTests/smokeTest1/auctionOpen.js new file mode 100644 index 00000000..2895bdb9 --- /dev/null +++ b/scripts/dist/scripts/integrationTests/smokeTest1/auctionOpen.js @@ -0,0 +1,99 @@ +import assert from "assert"; +//@note Wrap functions into a try catch to avoid breaking thread, log errors correctly +export const smokeTest = async ({ provider, vaultFacade: vault, ethFacade: eth, constants: { depositAmount }, getLPUnlockedBalanceAll, depositAll, withdrawAll, getBalancesAll, getLiquidityProviderAccounts }) => { + const liquidityProviderAccounts = getLiquidityProviderAccounts(2); + //Approve A for depositing + await eth.approval({ + owner: liquidityProviderAccounts[0], + amount: BigInt(100) * BigInt(depositAmount), + spender: vault.vaultContract.address, + }); + const ethBalancesBefore = await getBalancesAll(liquidityProviderAccounts); + const lpUnlockedBalancesBefore = await getLPUnlockedBalanceAll(liquidityProviderAccounts); + //Deposits + //1. Deposit from A with B as beneficiary + //2. Deposit from A for self + const depositAllArgs = [ + { + from: liquidityProviderAccounts[0], + beneficiary: liquidityProviderAccounts[1].address, + amount: depositAmount, + }, + { + from: liquidityProviderAccounts[0], + beneficiary: liquidityProviderAccounts[0].address, + amount: depositAmount, + }, + ]; + await depositAll(depositAllArgs); + //Debug + const lpUnlockedBalancesAfter = await getLPUnlockedBalanceAll(liquidityProviderAccounts); + const ethBalancesAfter = await getBalancesAll(liquidityProviderAccounts); + //Asserts + //1) Check liquidity for A has increased by depositAmount + //2) Check liquidity for B has increased by depositAmount + //3) Check eth balance for A has dropped by 2*depositAmount + checkpoint1({ + lpUnlockedBalancesBefore, + lpUnlockedBalancesAfter, + ethBalancesBefore, + ethBalancesAfter, + depositAmount, + }); + //Withdraws + //Withdraw constants.depositAmount/2 from vaultContract for A and B positions + //Withdraw again for B, greater than depositAmount/2 + 1, should revert + const withdrawAllData = [ + { + account: liquidityProviderAccounts[0], + amount: BigInt(depositAmount) / BigInt(2), + }, + { + account: liquidityProviderAccounts[1], + amount: BigInt(depositAmount) / BigInt(2), + }, + ]; + await withdrawAll(withdrawAllData); + const lpUnlockedBalancesAfterWithdraw = await getLPUnlockedBalanceAll(liquidityProviderAccounts); + const ethBalancesAfterWithdraw = await getBalancesAll(liquidityProviderAccounts); + try { + await vault.withdraw({ + account: liquidityProviderAccounts[1], + amount: BigInt(depositAmount) / BigInt(2) + BigInt(1), + }); + throw Error("Should have reverted"); + } + catch (err) { + const error = err; + assert(error.message !== "Should have reverted"); + } + const lpUnlockedBalancesAfterWithdraw2 = await getLPUnlockedBalanceAll(liquidityProviderAccounts); + //Asserts + //1) Check liquidity for A & B has decreased by depositAmount/2 + //2) Check balance for A & B has increased by depositAmount/2 + checkpoint2({ + lpUnlockedBalancesBefore: lpUnlockedBalancesAfter, + lpUnlockedBalancesAfter: lpUnlockedBalancesAfterWithdraw, + ethBalancesBefore: ethBalancesAfter, + ethBalancesAfter: ethBalancesAfterWithdraw, + depositAmount, + }); +}; +function checkpoint1({ lpUnlockedBalancesBefore, lpUnlockedBalancesAfter, ethBalancesBefore, ethBalancesAfter, depositAmount, }) { + assert(BigInt(lpUnlockedBalancesAfter[0]) === + BigInt(lpUnlockedBalancesBefore[0]) + BigInt(depositAmount), "liquidity A mismatch"); + assert(BigInt(lpUnlockedBalancesAfter[1]) === + BigInt(lpUnlockedBalancesBefore[1]) + BigInt(depositAmount), "liquidity B mismatch"); + assert(BigInt(ethBalancesBefore[0]) === + BigInt(ethBalancesAfter[0]) + BigInt(2) * BigInt(depositAmount), "Eth balance for a mismatch"); +} +function checkpoint2({ lpUnlockedBalancesAfter, lpUnlockedBalancesBefore, ethBalancesAfter, ethBalancesBefore, depositAmount, }) { + assert(BigInt(lpUnlockedBalancesBefore[0]) == + BigInt(lpUnlockedBalancesAfter[0]) + BigInt(depositAmount) / BigInt(2), "Mismatch A liquidity"); + assert(BigInt(lpUnlockedBalancesBefore[1]) == + BigInt(lpUnlockedBalancesAfter[1]) + BigInt(depositAmount) / BigInt(2), "Mismatch B liquidity"); + assert(BigInt(ethBalancesBefore[0]) == + BigInt(ethBalancesAfter[0]) - BigInt(depositAmount) / BigInt(2), "Mismatch A balance"); + assert(BigInt(ethBalancesBefore[1]) == + BigInt(ethBalancesAfter[1]) - BigInt(depositAmount) / BigInt(2), "Mismatch B balance"); +} diff --git a/scripts/dist/scripts/integrationTests/smokeTest1/auctionStart.js b/scripts/dist/scripts/integrationTests/smokeTest1/auctionStart.js new file mode 100644 index 00000000..e08901d2 --- /dev/null +++ b/scripts/dist/scripts/integrationTests/smokeTest1/auctionStart.js @@ -0,0 +1,105 @@ +import { getAccount } from "../../utils/helpers/common"; +import { getOptionRoundFacade } from "../../utils/helpers/setup"; +import assert from "assert"; +import { mineNextBlock } from "../../utils/katana"; +export const smokeTest = async ({ provider, vaultFacade, constants, getLPUnlockedBalanceAll, getLPLockedBalanceAll, getBalancesAll, approveAll, startAuctionBystander, getLiquidityProviderAccounts, getOptionBidderAccounts, }) => { + const optionRoundFacade = await getOptionRoundFacade(provider, vaultFacade.vaultContract); + const devAccount = getAccount("dev", provider); + try { + await vaultFacade.startAuction(devAccount); + throw Error("Should have reverted"); + } + catch (err) { + const error = err; + assert(error.message !== "Should have reverted", error.message); + //Failure expected when contracts are changed to revert + } + const stateAfter = await optionRoundFacade.optionRoundContract.get_state(); + const liquidityProviderAccounts = getLiquidityProviderAccounts(2); + const optionBidderAccounts = getOptionBidderAccounts(2); + assert(stateAfter.activeVariant() === "Open", `Expected:Open\nReceived:${stateAfter.activeVariant()}`); + await startAuctionBystander(); + const unlockedBalances = await getLPUnlockedBalanceAll(liquidityProviderAccounts); + const lockedBalances = await getLPLockedBalanceAll(liquidityProviderAccounts); + const totalLockedAmount = await vaultFacade.getTotalLocked(); + const totalUnlockedAmount = await vaultFacade.getTotalUnLocked(); + //Asserts + checkpoint1({ + unlockedBalances, + lockedBalances, + totalLockedAmount, + totalUnlockedAmount, + depositAmount: constants.depositAmount, + }); + //Approve OptionBidders + const approveAllData = [ + { + owner: optionBidderAccounts[0], + amount: BigInt("90000000000000000000"), + spender: optionRoundFacade.optionRoundContract.address, + }, + { + owner: optionBidderAccounts[1], + amount: BigInt("90000000000000000000"), + spender: optionRoundFacade.optionRoundContract.address, + }, + ]; + await approveAll(approveAllData); + await mineNextBlock(provider.channel.nodeUrl); + //Place bids according to story script + const reservePrice = await optionRoundFacade.getReservePrice(); + const totalOptionAvailable = await optionRoundFacade.getTotalOptionsAvailable(); + const ethBalancesBefore = await getBalancesAll(optionBidderAccounts); + const placeBidsData = [ + { + from: optionBidderAccounts[0], + amount: BigInt(totalOptionAvailable) / BigInt(2), + price: BigInt(3) * BigInt(reservePrice), + }, + { + from: optionBidderAccounts[1], + amount: BigInt(totalOptionAvailable) / BigInt(2), + price: BigInt(2) * BigInt(reservePrice), + }, + { + from: optionBidderAccounts[1], + amount: BigInt(totalOptionAvailable) / BigInt(2), + price: BigInt(reservePrice), + }, + ]; + await optionRoundFacade.placeBidsAll(placeBidsData); + const ethBalancesAfter = await getBalancesAll(optionBidderAccounts); + const bidArrays = await optionRoundFacade.getBidsForAll(optionBidderAccounts); + checkpoint2({ + ethBalancesBefore, + ethBalancesAfter, + bidArrays, + reservePrice, + totalOptionAvailable, + }); +}; +async function checkpoint1({ lockedBalances, unlockedBalances, totalLockedAmount, totalUnlockedAmount, depositAmount, }) { + assert(Number(unlockedBalances[0]) === 0, `UnlockedBalanceA 0 expected, found ${unlockedBalances[0]}`); + assert(Number(unlockedBalances[1]) === 0, `UnlockedBalanceB 0 expected, found ${unlockedBalances[1]}`); + assert(BigInt(lockedBalances[0]) === BigInt(depositAmount) / BigInt(2), `LockedBalanceA ${BigInt(depositAmount) / BigInt(2)} expected, found ${lockedBalances[0]}`); + assert(BigInt(lockedBalances[1]) === BigInt(depositAmount) / BigInt(2), `LockedBalanceB ${BigInt(depositAmount) / BigInt(2)} expected, found ${lockedBalances[1]}`); + assert(BigInt(totalUnlockedAmount) === BigInt(0), `Total unlocked 0 expected, found ${totalUnlockedAmount}`); + assert(BigInt(totalLockedAmount) === BigInt(depositAmount), `Total Locked amount ${BigInt(depositAmount)} expected, found ${totalLockedAmount}`); +} +async function checkpoint2({ ethBalancesBefore, ethBalancesAfter, bidArrays, totalOptionAvailable, reservePrice, }) { + console.log("Bids from A:\n", bidArrays[0], "\nBids from B:\n", bidArrays[1]); + assert(BigInt(ethBalancesBefore[0]) - BigInt(ethBalancesAfter[0]) === + (BigInt(3) * BigInt(reservePrice) * BigInt(totalOptionAvailable)) / + BigInt(2), "Error A"); + assert(BigInt(ethBalancesBefore[1]) - BigInt(ethBalancesAfter[1]) === + (BigInt(3) * BigInt(reservePrice) * BigInt(totalOptionAvailable)) / + BigInt(2), "Error B"); + assert(bidArrays[0].length === 1, `No. of Bids for A wrong,\n Expected:${BigInt(totalOptionAvailable)}\n Received:${bidArrays[0]?.length}`); + assert(bidArrays[0][0].amount === BigInt(totalOptionAvailable) / BigInt(2), "Bid for A amount wrong"); + assert(bidArrays[0][0].price === BigInt(3) * BigInt(reservePrice), "Bid for A price wrong"); + assert(bidArrays[1].length === 2, "No. of Bids for B wrong"); + assert(bidArrays[1][0].amount === BigInt(totalOptionAvailable) / BigInt(2), "First bid for B amount wrong"); + assert(bidArrays[1][0].price === BigInt(2) * BigInt(reservePrice), "First bid for B price wrong"); + assert(bidArrays[1][1].amount === BigInt(totalOptionAvailable) / BigInt(2), "Second bid for B amount wrong "); + assert(BigInt(bidArrays[1][1].price) === BigInt(reservePrice), `Second bid for B price wrong.\n Expected:${reservePrice}, Actual:${bidArrays[1][0].price}`); +} diff --git a/scripts/dist/scripts/integrationTests/smokeTest1/exerciseOptions.js b/scripts/dist/scripts/integrationTests/smokeTest1/exerciseOptions.js new file mode 100644 index 00000000..81f61eb3 --- /dev/null +++ b/scripts/dist/scripts/integrationTests/smokeTest1/exerciseOptions.js @@ -0,0 +1,37 @@ +import { getOptionRoundFacade } from "../../utils/helpers/setup"; +import assert from "assert"; +export const smokeTest = async ({ provider, vaultFacade, getBalancesAll, getOptionBidderAccounts }) => { + const optionRoundFacade = await getOptionRoundFacade(provider, vaultFacade.vaultContract, true); + const optionBidderAccounts = getOptionBidderAccounts(3); + const ethBalancesBefore = await getBalancesAll(optionBidderAccounts); + const exerciseOptionsAllArgs = optionBidderAccounts.map((bidder) => { + return { from: bidder }; + }); + await optionRoundFacade.exerciseOptionsAll(exerciseOptionsAllArgs); + const ethBalancesAfter = await getBalancesAll(optionBidderAccounts); + const totalPayout = await optionRoundFacade.getTotalPayout(); + checkpoint1({ + ethBalancesBefore, + ethBalancesAfter, + totalPayout, + }); + await optionRoundFacade.exerciseOptionsAll(exerciseOptionsAllArgs); + const ethBalancesAfterTwice = await getBalancesAll(optionBidderAccounts); + checkpoint2({ + ethBalancesAfter, + ethBalancesAfterTwice, + }); +}; +async function checkpoint1({ ethBalancesBefore, ethBalancesAfter, totalPayout, }) { + // - Test C & D exercise their options + // - Test C’s ETH balance increases by 1/4 total payout + assert(BigInt(ethBalancesAfter[0]) - BigInt(ethBalancesBefore[0]) === + BigInt(totalPayout) / BigInt(4), "Eth Balance for C mismatch"); + // - Test D’s ETH balance increases by 3/4 total payout + assert(BigInt(ethBalancesAfter[1]) - BigInt(ethBalancesBefore[1]) === + (BigInt(3) * BigInt(totalPayout)) / BigInt(4), "Eth Balance for D mismatch"); +} +async function checkpoint2({ ethBalancesAfter, ethBalancesAfterTwice, }) { + assert(ethBalancesAfter[0] === ethBalancesAfterTwice[0], "Balance changed on exercising again"); + assert(ethBalancesAfter[1] === ethBalancesAfterTwice[1], "Balance changed on exercising again"); +} diff --git a/scripts/dist/scripts/integrationTests/smokeTest1/index.js b/scripts/dist/scripts/integrationTests/smokeTest1/index.js new file mode 100644 index 00000000..740b5c8d --- /dev/null +++ b/scripts/dist/scripts/integrationTests/smokeTest1/index.js @@ -0,0 +1,6 @@ +export { smokeTest as auctionOpenTests } from "./auctionOpen"; +export { smokeTest as auctionStartTests } from "./auctionStart"; +export { smokeTest as auctionEndTetsts } from "./auctionEnd"; +export { smokeTest as refundTokenizeBids } from "./refundBid"; +export { smokeTest as optionSettle } from "./optionSettle"; +export { smokeTest as exerciseOptions } from "./exerciseOptions"; diff --git a/scripts/dist/scripts/integrationTests/smokeTest1/optionSettle.js b/scripts/dist/scripts/integrationTests/smokeTest1/optionSettle.js new file mode 100644 index 00000000..a87fea27 --- /dev/null +++ b/scripts/dist/scripts/integrationTests/smokeTest1/optionSettle.js @@ -0,0 +1,82 @@ +import { getAccount } from "../../utils/helpers/common"; +import { getOptionRoundFacade } from "../../utils/helpers/setup"; +import assert from "assert"; +export const smokeTest = async ({ provider, vaultFacade, constants: { depositAmount, settlementPrice, volatility, reservePrice }, ethFacade, getLPUnlockedBalanceAll, settleOptionRoundBystander, getLiquidityProviderAccounts, getOptionBidderAccounts, }) => { + const optionRoundFacade = await getOptionRoundFacade(provider, vaultFacade.vaultContract); + const liquidityProviderAccounts = getLiquidityProviderAccounts(2); + const devAccount = getAccount("dev", provider); + try { + let jobRequest = await optionRoundFacade.createJobRequest(); + await vaultFacade.settleOptionRound(devAccount, jobRequest); + throw Error("Should have reverted"); + } + catch (err) { + const error = err; + assert(error.message !== "Should have reverted", error.message); + //Failure expected when contracts are changed to revert + } + const state = await optionRoundFacade.optionRoundContract.get_state(); + assert(state.activeVariant() === "Running", `Expected:Running\nReceived:${state.activeVariant()}`); + const totalPremiums = await optionRoundFacade.getTotalPremiums(); + const ethBalanceBefore = await ethFacade.getBalance(liquidityProviderAccounts[0].address); + await vaultFacade.withdraw({ + account: liquidityProviderAccounts[0], + amount: BigInt(totalPremiums) / BigInt(4), + }); + const ethBalanceAfter = await ethFacade.getBalance(liquidityProviderAccounts[0].address); + const lpUnlockedBalanceAfter = await vaultFacade.getLPLockedBalance(liquidityProviderAccounts[0].address); + const totalUnlocked = await vaultFacade.getTotalUnLocked(); + checkpoint1({ + ethBalanceBefore, + ethBalanceAfter, + lpUnlockedBalanceAfter, + totalUnlocked, + totalPremiums, + }); + const marketData = { + settlementPrice, + volatility, + reservePrice, + }; + await settleOptionRoundBystander(marketData); + const stateAfter = await optionRoundFacade.optionRoundContract.get_state(); + const lpUnlockedBalances = await getLPUnlockedBalanceAll(liquidityProviderAccounts); + const totalPayout = await optionRoundFacade.getTotalPayout(); + const totalLocked = await vaultFacade.getTotalLocked(); + const totalUnlockedAfter = await vaultFacade.getTotalUnLocked(); + assert(stateAfter.activeVariant() === "Settled", `Expected:Running\nReceived:${stateAfter.activeVariant()}`); + checkpoint2({ + lpUnlockedBalances, + totalPremiums, + totalPayout, + totalLocked, + totalUnlocked: totalUnlockedAfter, + depositAmount, + }); +}; +async function checkpoint1({ ethBalanceBefore, ethBalanceAfter, lpUnlockedBalanceAfter, totalUnlocked, totalPremiums, }) { + console.log("ethBalanceAfter:", ethBalanceAfter, "\nethBalanceBefore:", ethBalanceBefore, "\ntotalPremiums", totalPremiums, "lpUnlockedBalanceAfter:", lpUnlockedBalanceAfter); + assert(BigInt(ethBalanceAfter) - BigInt(ethBalanceBefore) === + BigInt(totalPremiums) / BigInt(4), "LP Unlocked for A mismatch"); + assert(BigInt(lpUnlockedBalanceAfter) === BigInt(totalPremiums) / BigInt(4), "LP Unlocked for B mismatch"); + assert(BigInt(totalUnlocked) === (BigInt(3) * BigInt(totalPremiums)) / BigInt(4), "LP Locked for A mismatch"); +} +async function checkpoint2({ lpUnlockedBalances, totalPremiums, totalPayout, totalLocked, totalUnlocked, depositAmount, }) { + // - A’s unlocked balance should be 1/2 deposit amount + 1/4 total premiums - 1/2 total payout + assert(BigInt(lpUnlockedBalances[0]) === + BigInt(depositAmount) / BigInt(2) + + BigInt(totalPremiums) / BigInt(4) - + BigInt(totalPayout) / BigInt(2), "lpUnlocked for A Mismatch"); + // - B’s unlocked balance should be 1/2 deposit amount + 1/2 total premiums - 1/2 total payout + assert(lpUnlockedBalances[1] === + BigInt(depositAmount) / BigInt(2) + + BigInt(totalPremiums) / BigInt(2) - + BigInt(totalPayout) / BigInt(2), "lpUnlocked for B Mismatch"); + // - The total locked should == 0 + assert(Number(totalLocked) === 0, `total Locked should be zero, found: ${totalLocked}`); + // - The total unlocked should == deposit amount + 3/4 total premiums - total payout + assert(BigInt(totalUnlocked) == + BigInt(depositAmount) + + (BigInt(3) * BigInt(totalPremiums)) / BigInt(4) - + BigInt(totalPayout), `totalUnlocked mismatch \n${totalUnlocked}\n${lpUnlockedBalances}`); +} diff --git a/scripts/dist/scripts/integrationTests/smokeTest1/refundBid.js b/scripts/dist/scripts/integrationTests/smokeTest1/refundBid.js new file mode 100644 index 00000000..c2470414 --- /dev/null +++ b/scripts/dist/scripts/integrationTests/smokeTest1/refundBid.js @@ -0,0 +1,83 @@ +import { getAccount } from "../../utils/helpers/common"; +import { getOptionRoundFacade, } from "../../utils/helpers/setup"; +import assert from "assert"; +import { ERC20Facade } from "../../utils/facades/erc20Facade"; +export const smokeTest = async ({ provider, vaultFacade: vault, getBalancesAll, getOptionBidderAccounts }) => { + const optionRoundFacade = await getOptionRoundFacade(provider, vault.vaultContract); + const optionRoundERC20Contract = new ERC20Facade(optionRoundFacade.optionRoundContract.address, provider); + const devAccount = getAccount("dev", provider); + const totalOptionAvailable = await optionRoundFacade.getTotalOptionsAvailable(); + const reservePrice = await optionRoundFacade.getReservePrice(); + const optionBidderAccounts = getOptionBidderAccounts(3); + console.log("totalOptionsAvailable", totalOptionAvailable); + const balancesBefore = await getBalancesAll(optionBidderAccounts); + try { + await optionRoundFacade.refundUnusedBids({ + from: devAccount, + optionBidder: optionBidderAccounts[0].address, + }); + await optionRoundFacade.refundUnusedBids({ + from: devAccount, + optionBidder: optionBidderAccounts[1].address, + }); + } + catch (err) { + console.log("Error while refunding the unused bids", err); + } + const balancesAfter = await getBalancesAll(optionBidderAccounts); + checkpoint1({ + balancesBefore, + balancesAfter, + totalOptionAvailable, + reservePrice, + }); + const optionBalancesBefore = await optionRoundFacade.getTotalOptionsBalanceForAll(optionBidderAccounts); + try { + await optionRoundFacade.tokenizeOptions({ + from: optionBidderAccounts[0], + }); + } + catch (err) { + console.log("Error while tokenizing the option", err); + } + try { + optionRoundERC20Contract.erc20Contract.connect(optionBidderAccounts[0]); + await optionRoundERC20Contract.erc20Contract.approve(optionRoundERC20Contract.erc20Contract.address, BigInt(totalOptionAvailable) / BigInt(4)); + // @dev @note: ideally this should be working: + // await optionRoundERC20Contract.approval({ + // owner: optionBidderAccounts[0], + // amount: BigInt(totalOptionAvailable) / BigInt(4), + // spender: optionRoundERC20Contract.erc20Contract.address, + // }); + await optionRoundERC20Contract.erc20Contract.transfer(optionBidderAccounts[1].address, BigInt(totalOptionAvailable) / BigInt(4)); + } + catch (err) { + console.log("Error while transferring the tokenized options", err); + } + const optionBalancesAfter = await optionRoundFacade.getTotalOptionsBalanceForAll(optionBidderAccounts); + checkpoint2({ + optionBalancesBefore, + optionBalancesAfter, + totalOptionAvailable, + }); +}; +async function checkpoint1({ balancesBefore, balancesAfter, totalOptionAvailable, reservePrice, }) { + assert(BigInt(balancesBefore[0]) + + (BigInt(totalOptionAvailable) / BigInt(2)) * BigInt(reservePrice) === + BigInt(balancesAfter[0]), "Unused bids balance fail"); + assert(BigInt(balancesBefore[1]) + + (BigInt(totalOptionAvailable) / BigInt(2)) * BigInt(reservePrice) === + BigInt(balancesAfter[1]), "Unused bids balance fail"); +} +async function checkpoint2({ optionBalancesBefore, optionBalancesAfter, totalOptionAvailable, }) { + assert(BigInt(optionBalancesBefore[0]) === BigInt(optionBalancesBefore[1]), "Intial options should be equal"); + assert(BigInt(optionBalancesBefore[0]) + BigInt(optionBalancesBefore[1]) === + BigInt(totalOptionAvailable), "Intial sum of options should be total options available"); + assert(BigInt(optionBalancesAfter[0]) + BigInt(optionBalancesAfter[1]) === + BigInt(totalOptionAvailable), "After transfer sum of options should be total options available"); + assert(BigInt(optionBalancesBefore[0]) / BigInt(2) === + BigInt(optionBalancesAfter[0]), `Final option balance of C should be half of initial.\n ${optionBalancesBefore[0]}\n${optionBalancesAfter[0]}`); + assert(BigInt(optionBalancesBefore[1]) + + BigInt(optionBalancesBefore[0]) / BigInt(2) === + BigInt(optionBalancesAfter[1]), "Final option balance of D should be inital + half of C"); +} diff --git a/scripts/dist/scripts/integrationTests/smokeTesting.js b/scripts/dist/scripts/integrationTests/smokeTesting.js new file mode 100644 index 00000000..20e34264 --- /dev/null +++ b/scripts/dist/scripts/integrationTests/smokeTesting.js @@ -0,0 +1,10 @@ +import { auctionEndTetsts, auctionOpenTests, auctionStartTests, exerciseOptions, optionSettle, refundTokenizeBids, } from "./smokeTest1"; +async function smokeTesting(testRunner) { + await auctionOpenTests(testRunner); + await auctionStartTests(testRunner); + await auctionEndTetsts(testRunner); + await refundTokenizeBids(testRunner); + await optionSettle(testRunner); + await exerciseOptions(testRunner); +} +export { smokeTesting }; diff --git a/scripts/dist/scripts/simulation.js b/scripts/dist/scripts/simulation.js new file mode 100644 index 00000000..067b321d --- /dev/null +++ b/scripts/dist/scripts/simulation.js @@ -0,0 +1,29 @@ +import { getAccount, getProvider } from "./utils/helpers/common"; +import { declareContracts } from "./utils/deployment/declareContracts"; +import { deployContracts } from "./utils/deployment/deployContracts"; +import { TestRunner } from "./utils/facades/TestRunner"; +import { simulationTesting } from "./simulationTests"; +import { ERC20Facade } from "./utils/facades/erc20Facade"; +async function main(environment, port) { + const provider = getProvider(environment, port); + const devAccount = getAccount(environment, provider); + let hashes = await declareContracts(devAccount); + let { ethAddress, vaultAddress } = await deployContracts(environment, devAccount, hashes); + //@Note remove this when testRunner refactor by Jithin is finished + const constants = { + depositAmount: BigInt(10000000000000), + reservePrice: BigInt(4000000000), + strikePrice: BigInt(8000000000), + settlementPrice: BigInt(16000000000), + volatility: 10, + capLevel: 5000, + }; + const testRunner = new TestRunner(provider, vaultAddress, ethAddress, constants); + const feeTokenFacade = new ERC20Facade("0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", provider); + feeTokenFacade.erc20Contract.connect(devAccount); + await feeTokenFacade.erc20Contract.transfer("0x0134f47366096198eb8f86e3ae6b075d399ca7abd918a56e01bb3b24963c2f75", BigInt(1000000000000000000)); + await feeTokenFacade.erc20Contract.transfer("0x01577908d02E0a3A6B243A149Eb91BB4514f3aAb948CFE63b2f8bb52397618D4", BigInt(1000000000000000000)); + await testRunner.ethFacade.supplyERC20(devAccount, provider, ethAddress, vaultAddress); + await simulationTesting(testRunner); +} +main(process.argv[2], process.argv[3]); diff --git a/scripts/dist/scripts/simulationData/marketData.json b/scripts/dist/scripts/simulationData/marketData.json new file mode 100644 index 00000000..d54a0900 --- /dev/null +++ b/scripts/dist/scripts/simulationData/marketData.json @@ -0,0 +1,170 @@ +[ + { + "starting_timestamp": 1671082204, + "ending_timestamp": 1673760608, + "reserve_price": 1133806091.8973722, + "strike_price": 17635296321.331875, + "settlement_price": 17635296321.87111, + "volatility": 3000 + }, + { + "starting_timestamp": 1673760608, + "ending_timestamp": 1676439005, + "reserve_price": 2289497207.1168942, + "strike_price": 21386087366.87111, + "settlement_price": 21386087366.817963, + "volatility": 3000 + }, + { + "starting_timestamp": 1676439005, + "ending_timestamp": 1678858200, + "reserve_price": 1279595660.5008752, + "strike_price": 29034664790.817963, + "settlement_price": 29034664790.02064, + "volatility": 3000 + }, + { + "starting_timestamp": 1678858200, + "ending_timestamp": 1681536603, + "reserve_price": 875665408.7313896, + "strike_price": 38493145481.02064, + "settlement_price": 38493145481.663445, + "volatility": 3000 + }, + { + "starting_timestamp": 1681536603, + "ending_timestamp": 1684128611, + "reserve_price": 2633366765.449701, + "strike_price": 25439135433.663445, + "settlement_price": 25439135433.31648, + "volatility": 3000 + }, + { + "starting_timestamp": 1684128611, + "ending_timestamp": 1686807007, + "reserve_price": 1356220491.835023, + "strike_price": 74555216167.31648, + "settlement_price": 74555216167.505894, + "volatility": 3000 + }, + { + "starting_timestamp": 1686807007, + "ending_timestamp": 1689399000, + "reserve_price": 1952777064.7434845, + "strike_price": 20136894952.505894, + "settlement_price": 20136894952.938293, + "volatility": 3000 + }, + { + "starting_timestamp": 1689399000, + "ending_timestamp": 1692077411, + "reserve_price": 2146702361.445386, + "strike_price": 22828235819.938293, + "settlement_price": 22828235819.848763, + "volatility": 3000 + }, + { + "starting_timestamp": 1692077411, + "ending_timestamp": 1694755801, + "reserve_price": 1644783441.6741364, + "strike_price": 19320316991.848763, + "settlement_price": 19320316991.733692, + "volatility": 3000 + }, + { + "starting_timestamp": 1694755801, + "ending_timestamp": 1697347808, + "reserve_price": 1467840115.5542483, + "strike_price": 15696616976.733692, + "settlement_price": 15696616976.569965, + "volatility": 3000 + }, + { + "starting_timestamp": 1697347808, + "ending_timestamp": 1700026208, + "reserve_price": 554611569.0124147, + "strike_price": 7419675169.569965, + "settlement_price": 7419675169.247475, + "volatility": 3000 + }, + { + "starting_timestamp": 1700026208, + "ending_timestamp": 1702618206, + "reserve_price": 3834880492.891962, + "strike_price": 37644135519.247475, + "settlement_price": 37644135519.43376, + "volatility": 3000 + }, + { + "starting_timestamp": 1702618206, + "ending_timestamp": 1705296605, + "reserve_price": 5010998700.134231, + "strike_price": 39788123292.43376, + "settlement_price": 39788123292.067184, + "volatility": 3000 + }, + { + "starting_timestamp": 1705296605, + "ending_timestamp": 1707975009, + "reserve_price": 2102480779.8673804, + "strike_price": 26352409279.067184, + "settlement_price": 26352409279.14552, + "volatility": 3000 + }, + { + "starting_timestamp": 1707975009, + "ending_timestamp": 1710480615, + "reserve_price": 1358716570.77586, + "strike_price": 40189317122.14552, + "settlement_price": 40189317122.033165, + "volatility": 3000 + }, + { + "starting_timestamp": 1710480615, + "ending_timestamp": 1713159006, + "reserve_price": 7431323768.285521, + "strike_price": 58500408944.033165, + "settlement_price": 58500408944.007843, + "volatility": 3000 + }, + { + "starting_timestamp": 1713159006, + "ending_timestamp": 1715751017, + "reserve_price": 1105329970.4182699, + "strike_price": 24358911017.007843, + "settlement_price": 24358911017.434905, + "volatility": 3000 + }, + { + "starting_timestamp": 1715751017, + "ending_timestamp": 1718429397, + "reserve_price": 517729381.0134145, + "strike_price": 5681275902.434905, + "settlement_price": 5681275902.398533, + "volatility": 3000 + }, + { + "starting_timestamp": 1718429397, + "ending_timestamp": 1721021409, + "reserve_price": 1008885496.8860922, + "strike_price": 10706888590.398533, + "settlement_price": 10706888590.4517756, + "volatility": 3000 + }, + { + "starting_timestamp": 1721021409, + "ending_timestamp": 1721160731, + "reserve_price": 347501891.0070594, + "strike_price": 3907948839.4517756, + "settlement_price": 3907948839.922286, + "volatility": 3000 + }, + { + "starting_timestamp": 1721160731, + "ending_timestamp": 1721160731, + "reserve_price": 361402055.1651912, + "strike_price": 5191304671.922286, + "settlement_price": 5191304671.922286, + "volatility": 3000 + } +] diff --git a/scripts/dist/scripts/simulationTests/index.js b/scripts/dist/scripts/simulationTests/index.js new file mode 100644 index 00000000..4ff77a10 --- /dev/null +++ b/scripts/dist/scripts/simulationTests/index.js @@ -0,0 +1,66 @@ +import { RoundSimulator, } from "../utils/facades/RoundSimulator"; +import marketData from "../simulationData/marketData.json" assert { type: "json" }; +import { generateSimulationParams, getOptionRoundContract, } from "../utils/helpers/setup"; +import fs from "fs"; +async function simulationTesting(testRunner) { + const optionRoundContract = await getOptionRoundContract(testRunner.provider, testRunner.vaultFacade.vaultContract); + const simulator = new RoundSimulator(testRunner, optionRoundContract); + const simulationSheets = generateSheet(); + const simulationParams = generateSimulationParams(testRunner, simulationSheets); + const data = { results: [] }; + for (const roundParams of simulationParams) { + const roundData = await simulator.simulateRound(roundParams); + data.results.push(roundData); + } + const stringified = JSON.stringify(data); + fs.writeFile(`./simulationData/simulationOutput/simulationResults-${Math.floor(Date.now() / 1000)}.json`, stringified, "utf8", () => { }); +} +export { simulationTesting }; +const initial = { + liquidityProviders: [1, 2], + depositAmounts: ["50000000000000", "50000000000000"], + optionBidders: [1, 3], +}; +const repeating = { + liquidityProviders: [], + depositAmounts: [], + optionBidders: [1, 3], +}; +export const generateSheet = () => { + const simulationMarketData = marketData.map((data) => { + return { + settlementPrice: Math.floor(data.settlement_price), + volatility: data.volatility, + reservePrice: Math.floor(data.reserve_price), + strikePrice: Math.floor(data.strike_price), + startTime: data.starting_timestamp, + endTime: data.ending_timestamp, + }; + }); + const simulationSheet = simulationMarketData.map((marketData, index) => { + if (index == 0) { + return { + ...initial, + bidPrices: initial.optionBidders.map((bidder) => { + return marketData.reservePrice; + }), + marketData, + bidAmounts: [Math.random(), Math.random()], + withdrawals: [1, 2], + withdrawalAmounts: [Math.random() / 2, Math.random() / 2], + }; + } + else + return { + ...repeating, + bidAmounts: [Math.random(), Math.random()], + withdrawals: [1, 2], + withdrawalAmounts: [Math.random() / 2, Math.random() / 2], + bidPrices: initial.optionBidders.map((bidder) => { + return marketData.reservePrice; + }), + marketData, + }; + }); + return simulationSheet; +}; diff --git a/scripts/dist/scripts/smokeTests.js b/scripts/dist/scripts/smokeTests.js new file mode 100644 index 00000000..a8875751 --- /dev/null +++ b/scripts/dist/scripts/smokeTests.js @@ -0,0 +1,25 @@ +import { getAccount, getProvider } from "./utils/helpers/common"; +import { smokeTesting } from "./integrationTests/smokeTesting"; +import { declareContracts } from "./utils/deployment/declareContracts"; +import { deployContracts } from "./utils/deployment/deployContracts"; +import { TestRunner } from "./utils/facades/TestRunner"; +async function main(environment, port) { + const provider = getProvider(environment, port); + const devAccount = getAccount(environment, provider); + let hashes = await declareContracts(devAccount); + const constants = { + depositAmount: BigInt(10000000000000), + reservePrice: BigInt(4000000000), + strikePrice: BigInt(8000000000), + settlementPrice: BigInt(16000000000), + volatility: 10, + capLevel: 5000, + }; + console.log("HASHES", hashes); + let { ethAddress, vaultAddress } = await deployContracts(environment, devAccount, hashes); + const testRunner = new TestRunner(provider, vaultAddress, ethAddress, constants); + await testRunner.ethFacade.supplyERC20(devAccount, provider, ethAddress, vaultAddress); + //Can write to a file here and replace smoke test call to use multiple + await smokeTesting(testRunner); +} +main(process.argv[2], process.argv[3]); diff --git a/scripts/dist/scripts/utils/constants.js b/scripts/dist/scripts/utils/constants.js new file mode 100644 index 00000000..9a8441b1 --- /dev/null +++ b/scripts/dist/scripts/utils/constants.js @@ -0,0 +1,147 @@ +// constants.js +import { cairo } from "starknet"; +const nodeUrlMapping = { + production: "", + staging: "", + dev: `http://localhost`, +}; +const constructorArgs = { + dev: { + eth: { + supply: cairo.uint256(1e32), + recipientContractAddress: "0x7ce7089cb75a590b9485f6851d8998fa885494cc7a70dbae8f3db572586b8a8", + }, + vault: { + roundTransitionPeriod: 32000, + auctionRunTime: 23000, + optionRunTime: 23000, + kFactor: 10000, + }, + optionRound: "", + factRegistry: "", + }, +}; +const accountDetailsMapping = { + production: { + accountAddress: process.env.PRODUCTION_ACCOUNT_ADDRESS, + privateKey: process.env.PRODUCTION_PRIVATE_KEY, + ethAddress: "PRODUCTION_ETH_ADDRESS", + }, + staging: { + accountAddress: process.env.STAGING_ACCOUNT_ADDRESS, + privateKey: process.env.STAGING_PRIVATE_KEY, + ethAddress: "STAGING_ETH_ADDRESS", + }, + dev: { + accountAddress: "0x7ce7089cb75a590b9485f6851d8998fa885494cc7a70dbae8f3db572586b8a8", + privateKey: "0x74dfaa5aee853a8eb8033a31fa71e41c92d65a0abed9adc686bea9d28e2aed2", + }, +}; +const liquidityProviders = [ + { + account: "0x55df044c093d7b2408f6ad897f6d71a996afab8e1b17932e9c6438cb99593f", + privateKey: "0x33b46925f150922d9d459a4b29bd730db2106009bcde7030bca0c3762b67e0a", + }, + { + account: "0xdf31182cb5970fb2c2307bffa9efe43cfbe236ba55798be43f93e03c4ce8e8", + privateKey: "0x1ff4988a92b4fe18c70ff0d36ec8a84ae6b8980b436ede71d4735aac2142ce1", + }, + { + account: "0x14378f698a983f485552f8c6e645322cf6b54b605a7c274348c76f2a62967cd", + privateKey: "0x7e5c19e2beed021063f11e2b9fb30a13e2c3295ad77c2edd107da9dae415d8", + }, + { + account: "0x19c2021c2f95585dd079c6fea41e15fd6a92949054d0f56fc31e404558b3e3b", + privateKey: "0x381a747c5946b0d52ebe9fa9e90f8592c0fd37a1552bc7dea6bbc9409ffdbd6", + }, + { + account: "0x21c3596839d203b6ea83c7e7197a1640343b8111c7ce017767265ce0dbd2d88", + privateKey: "0x31cc6964f0fef400ceb51f9c82ba8a03a502d722be52d9e1ba2ec327f997327", + }, + { + account: "0x227bd40428ec6524d7431d53f7fb7e8b71b2d31fc08c87af6b1a9c1e2a77f22", + privateKey: "0xc90555ebd3c579f47e170426363830b7f07aa30fe2b69e4c11e080e9aadd7", + }, + { + account: "0x2be5a46e87d3882f242dd06e9dab68adca7b5cf18eee96ab21da39ecc5a61c6", + privateKey: "0x1b087397797b3cca4fd190e3c7d9029faedc71a57f1fbcac38da54af603ad47", + }, + { + account: "0x34c28bf49ac34bbb688e24b22bc25f63d3323a3d2ee78dced10a0e9b0a8ec13", + privateKey: "0x6c375c58c237617ca994ac0e348f9a6f69a27f584f534197add860ece9808ad", + }, + { + account: "0x3b4022611857234b93f9eb5e0ff4728bdc7390f9bf1446d96b5f05679a0d4f3", + privateKey: "0x4ceb7dd0adbf5139450e0cae092b54399c3e3d290195cb8b30ac19d6640ce8b", + }, + { + account: "0x4087745e395c247d305bed3af8c0be84a103d25c78f00f65d1a6369c63adb6f", + privateKey: "0x5e4eab06ea2fc5f4d98a563c573b67e624e212575bc716e44fb4acf7f2555ea", + }, +]; +const optionBidders = [ + { + account: "0x430861531f7d3536e3c3b77abda9b83d7b4d3e1ddc525583a58c0d528fe2a0b", + privateKey: "0x6e0911b16ae7583510ed4fb417425654762cb7eb36a29d424d156f2a2d1932e", + }, + { + account: "0x4798b763f1ae27b565da46e3b37db08095437de92db9ca33a0eb739f3abaf12", + privateKey: "0x13480410380180071a000490360f606518900fddf60f213a9649e6b7eb4b78b", + }, + { + account: "0x4d72d82b12efdceb0516af7cede2aa95e23d17eadfe2462ebc57d466b665c06", + privateKey: "0x402d805dd21e044b0168cd341d32aacbdf11a8e1038e559fce467f8b749caf3", + }, + { + account: "0x4d75495e10ee26cae76478b6e491646ff0a10e0a062db1555131e47b07b7d24", + privateKey: "0x100801800000000310080180000000010030000000000005106801800206800", + }, + { + account: "0x4ec7e8ce634b297c8d04ae0fb2ac49eb8151c04b2ca8e3be0f0341b01e4dac0", + privateKey: "0x13cab974c6b681790b669f48b24f18d7ddfd9bc1cba5bef5a44c583b3eea57d", + }, + { + account: "0x4f6b7ea31871dc220a7b5db702b094362beae1b68cf38bb179a83a89073fbd1", + privateKey: "0x66b06dd599390080d61b680aaa4c8b981a3659547dcf5e3cf7f3141c7864947", + }, + { + account: "0x50b92a5d75d89087e9d8b60eb3c67a571d543ddfdaac5a0053ba7be26a910e9", + privateKey: "0x4a499bebbc6d9f4c1318becc9ed941de4cc23b06706372bd1a68bb7dc10eaaa", + }, + { + account: "0x54ad5b2d856fc356e7509ba9f0a60b7e5aa7b8143f35144f757c5aef74ed780", + privateKey: "0x5f62f386261ba7f87c28dbf2c1bef150080d1bd2e6c9dc9af043865e23bd1ac", + }, + { + account: "0x58a65d5fc02f15b02a415e45150b5df47ddb31094b848732e9cab4efe6e5c3a", + privateKey: "0x1813b15096512db81553fc50b428d2cd436e3e4ce1c70a017ec9f18b2596029", + }, + { + account: "0x5a4ca27c7aba179075ef36586beeda4d7c5a326be6990f573639211f4f20763", + privateKey: "0x157f485d0415f0f6b9500118684a1ae060b903448a37df81d1a52e0c6fff57e", + }, +]; +const extras = [ + { + account: "0x5ab6259fccae957ce89be7e08c32d0b9ea5e2382da49c5bea57d33ee9eac379", + privateKey: "0xba3da1bf52a938b65196eeea873596bd90e893cbc2b18c777a432de08aba58", + }, + { + account: "0x6eea757999bebad427b0934c16dde89ef55fbb040094c48d792db3b71006523", + privateKey: "0x36048b7b1ba18597af8d1e5738d486cb98a0e0a0872f2d8fe00058c9b491376", + }, + { + account: "0x7886105c0bd55d4bd38b3b227b4c6ef9ca5751c4bdf32c0277456a02282b79a", + privateKey: "0x26a2bd3465580814a280f3bd688c01cdcc7f37a8b01362dc66b9f5d689e6cb8", + }, + { + account: "0x7cd5acf17d2fbd26519ea338c88791dcf6c7fc76980f514a7e89050ecb13fab", + privateKey: "0x785692961d914c1bd77c41f9ed90993f284935b59c52ec74c468ead1f7eb5c2", + }, +]; +let declaredContractsMapping = { + production: {}, + staging: {}, + dev: {}, +}; +export { nodeUrlMapping, constructorArgs, accountDetailsMapping, declaredContractsMapping, liquidityProviders, optionBidders, extras, }; diff --git a/scripts/dist/scripts/utils/deployment/declareContracts.js b/scripts/dist/scripts/utils/deployment/declareContracts.js new file mode 100644 index 00000000..361bb409 --- /dev/null +++ b/scripts/dist/scripts/utils/deployment/declareContracts.js @@ -0,0 +1,46 @@ +import ethSierra from "../../../target/dev/pitch_lake_Eth.contract_class.json" assert { type: "json" }; +import ethCasm from "../../../target/dev/pitch_lake_Eth.compiled_contract_class.json" assert { type: "json" }; +import vaultSierra from "../../../target/dev/pitch_lake_Vault.contract_class.json" assert { type: "json" }; +import vaultCasm from "../../../target/dev/pitch_lake_Vault.compiled_contract_class.json" assert { type: "json" }; +import optionRoundSieraa from "../../../target/dev/pitch_lake_OptionRound.contract_class.json" assert { type: "json" }; +import optionRoundCasm from "../../../target/dev/pitch_lake_OptionRound.compiled_contract_class.json" assert { type: "json" }; +import factRegistrySierra from "../../../target/dev/pitch_lake_FactRegistry.contract_class.json" assert { type: "json" }; +import factRegistryCasm from "../../../target/dev/pitch_lake_FactRegistry.compiled_contract_class.json" assert { type: "json" }; +async function declareContract(account, sierra, casm, placeholder) { + try { + const declareResult = await account.declare({ + contract: sierra, + casm: casm, + }); + console.log(`Declare result for ${placeholder}: `, declareResult); + return declareResult.class_hash; + } + catch (err) { + console.log(`Contract ${placeholder} is already declared`, err); + } +} +async function declareContracts(account) { + let ethHash = await declareContract(account, ethSierra, ethCasm, "eth"); + if (!ethHash) { + throw Error("Eth Deploy Failed"); + } + let vaultHash = await declareContract(account, vaultSierra, vaultCasm, "vault"); + if (!vaultHash) { + throw Error("Vault Deploy Failed"); + } + let optionRoundHash = await declareContract(account, optionRoundSieraa, optionRoundCasm, "optionRound"); + if (!optionRoundHash) { + throw Error("OptionRound Deploy Failed"); + } + let factRegistryHash = await declareContract(account, factRegistrySierra, factRegistryCasm, "factRegistry"); + if (!factRegistryHash) { + throw Error("FactRegistry Deploy Failed"); + } + return { + ethHash, + vaultHash, + optionRoundHash, + factRegistryHash, + }; +} +export { declareContract, declareContracts }; diff --git a/scripts/dist/scripts/utils/deployment/deployContracts.js b/scripts/dist/scripts/utils/deployment/deployContracts.js new file mode 100644 index 00000000..bbc128d2 --- /dev/null +++ b/scripts/dist/scripts/utils/deployment/deployContracts.js @@ -0,0 +1,62 @@ +// deployContracts.js +import { CallData, CairoCustomEnum } from "starknet"; +import vaultSierra from "../../../target/dev/pitch_lake_Vault.contract_class.json" assert { type: "json" }; +import { constructorArgs } from "../constants"; +async function deployEthContract(enviornment, account, classHash) { + let constructorArgsEth = [...Object.values(constructorArgs[enviornment].eth)]; + const deployResult = await account.deploy({ + classHash, + constructorCalldata: constructorArgsEth, + }); + console.log("ETH contract is deployed successfully at - ", deployResult); + return deployResult.contract_address[0]; +} +async function deployVaultContract(enviornment, account, contractAddresses, hashes) { + const contractCallData = new CallData(vaultSierra.abi); + let constants = constructorArgs[enviornment].vault; + const constructorCalldata = contractCallData.compile("constructor", { + round_transition_period: constants.roundTransitionPeriod, + auction_run_time: constants.auctionRunTime, + option_run_time: constants.optionRunTime, + eth_address: contractAddresses.ethContract, + vault_type: new CairoCustomEnum({ AtTheMoney: {} }), + fact_registry_address: contractAddresses.factRegistryContract, + option_round_class_hash: hashes.optionRound, + }); + const deployResult = await account.deploy({ + classHash: hashes.vault, + constructorCalldata: constructorCalldata, + }); + console.log("Vault contract is deployed successfully at - ", deployResult); + return deployResult.contract_address[0]; +} +async function deployFactRegistry(enviornment, account, factRegistryClassHash) { + const deployResult = await account.deploy({ + classHash: factRegistryClassHash, + }); + console.log("Market Aggregator contract is deployed successfully at - ", deployResult); + return deployResult.contract_address[0]; +} +async function deployContracts(enviornment, account, hashes) { + let ethAddress = await deployEthContract(enviornment, account, hashes.ethHash); + if (!ethAddress) { + throw Error("Eth deploy failed"); + } + let factRegistryAddress = await deployFactRegistry(enviornment, account, hashes.factRegistryHash); + if (!factRegistryAddress) { + throw Error("FactRegistry deploy failed"); + } + let vaultAddress = await deployVaultContract(enviornment, account, { + factRegistryContract: factRegistryAddress, + ethContract: ethAddress, + }, { optionRound: hashes.optionRoundHash, vault: hashes.vaultHash }); + if (!vaultAddress) { + throw Error("Eth deploy failed"); + } + return { + ethAddress, + factRegistryAddress, + vaultAddress, + }; +} +export { deployEthContract, deployFactRegistry, deployVaultContract, deployContracts, }; diff --git a/scripts/dist/scripts/utils/facades/RoundSimulator.js b/scripts/dist/scripts/utils/facades/RoundSimulator.js new file mode 100644 index 00000000..def3f672 --- /dev/null +++ b/scripts/dist/scripts/utils/facades/RoundSimulator.js @@ -0,0 +1,174 @@ +import { getOptionRoundContract } from "../helpers/setup"; +import { OptionRoundFacade } from "./optionRoundFacade"; +export class RoundSimulator { + testRunner; + optionRoundFacade; + lpAccounts; + bidderAccounts; + constructor(testRunner, optionRoundContract) { + this.testRunner = testRunner; + this.optionRoundFacade = new OptionRoundFacade(optionRoundContract); + this.lpAccounts = testRunner.getLiquidityProviderAccounts(5); + this.bidderAccounts = testRunner.getOptionBidderAccounts(5); + } + async simulateRound(params) { + const optionRoundContract = await getOptionRoundContract(this.testRunner.provider, this.testRunner.vaultFacade.vaultContract); + this.optionRoundFacade = new OptionRoundFacade(optionRoundContract); + //Add market agg setter here or somewhere in openState + const openStateData = await this.simulateOpenState(params.depositAllArgs); + const auctioningStateData = await this.simulateAuctioningState(params.bidAllArgs); + const optionsAvailable = await this.optionRoundFacade.getTotalOptionsAvailable(); + const runningStateData = await this.simulateRunningState(params.refundAllArgs, params.withdrawPremiumArgs); + const settledStateData = await this.simulateSettledState(params.exerciseOptionsAllArgs, params.withdrawalArgs, params.marketData); + const optionsSold = await this.optionRoundFacade.optionRoundContract.get_options_sold(); + const ethBalanceVault = await this.testRunner.ethFacade.getBalance(this.testRunner.vaultFacade.vaultContract.address); + const ethBalanceRound = await this.testRunner.ethFacade.getBalance(this.optionRoundFacade.optionRoundContract.address); + if (params.marketData.startTime && params.marketData.endTime) { + //Mock timestamps if present on the marketData + const difference = Number(params.marketData.endTime) - Number(params.marketData.startTime); + openStateData.timeStamp = + Number(params.marketData.startTime) + Math.floor(difference / 8); + auctioningStateData.timeStamp = + Number(params.marketData.startTime) + Math.floor((3 * difference) / 8); + runningStateData.timeStamp = + Number(params.marketData.startTime) + Math.floor((5 * difference) / 8); + settledStateData.timeStamp = + Number(params.marketData.startTime) + Math.floor((7 * difference) / 8); + } + return { + ethBalanceRound: ethBalanceRound.toString(), + ethBalanceVault: ethBalanceVault.toString(), + optionsAvailable: optionsAvailable.toString(), + optionsSold: optionsSold.toString(), + openStateData, + auctioningStateData, + runningStateData, + settledStateData, + }; + } + async captureLockedUnlockedBalances() { + const lpLockedBalancesBigInt = await this.testRunner.getLPLockedBalanceAll(this.lpAccounts); + const lpUnlockedBalancesBigint = await this.testRunner.getLPUnlockedBalanceAll(this.lpAccounts); + const lpLockedBalances = lpLockedBalancesBigInt.map((balance) => { + return balance.toString(); + }); + const lpUnlockedBalances = lpUnlockedBalancesBigint.map((balance) => { + return balance.toString(); + }); + return { lpLockedBalances, lpUnlockedBalances }; + } + async captureEthBalancesLiquidityProviders() { + const ethBalancesBigInt = await this.testRunner.getBalancesAll(this.lpAccounts); + const ethBalances = ethBalancesBigInt.map((balance) => { + return balance.toString(); + }); + return ethBalances; + } + async captureVaultBalances() { + const locked = await this.testRunner.vaultFacade.getTotalLocked(); + const unlocked = await this.testRunner.vaultFacade.getTotalUnLocked(); + return { + vaultLocked: locked.toString(), + vaultUnlocked: unlocked.toString(), + }; + } + async captureEthBalancesOptionBidders() { + const ethBalancesBigInt = await this.testRunner.getBalancesAll(this.bidderAccounts); + const ethBalances = ethBalancesBigInt.map((balance) => { + return balance.toString(); + }); + return ethBalances; + } + async simulateOpenState(depositAllArgs) { + await this.testRunner.depositAll(depositAllArgs); + const lockedUnlockedBalances = await this.captureLockedUnlockedBalances(); + const ethBalancesBidders = await this.captureEthBalancesOptionBidders(); + const vaultBalances = await this.captureVaultBalances(); + return { + lockedUnlockedBalances, + ethBalancesBidders, + vaultBalances, + }; + //Add market data setter abstraction after Jithin's merge + } + async simulateAuctioningState(bidAllArgs) { + await this.testRunner.startAuctionBystander(); + const lockedUnlockedBalances = await this.captureLockedUnlockedBalances(); + const vaultBalances = await this.captureVaultBalances(); + const optionsAvailable = await this.optionRoundFacade.getTotalOptionsAvailable(); + const bidAllArgsAdjusted = bidAllArgs.map((args) => { + return { + from: args.from, + amount: Math.floor(Number(args.amount) * Number(optionsAvailable)), + price: args.price, + }; + }); + const approvalArgs = bidAllArgsAdjusted.map((arg) => { + const data = { + owner: arg.from, + spender: this.optionRoundFacade.optionRoundContract.address, + amount: BigInt(arg.amount) * BigInt(arg.price), + }; + return data; + }); + await this.testRunner.approveAll(approvalArgs); + await this.optionRoundFacade.placeBidsAll(bidAllArgsAdjusted); + const ethBalancesBidders = await this.captureEthBalancesOptionBidders(); + return { + lockedUnlockedBalances, + ethBalancesBidders, + vaultBalances, + }; + } + async simulateRunningState(refundAllArgs, withdrawPremiumArgs) { + await this.testRunner.endAuctionBystander(); + const totalPremiums = await this.optionRoundFacade.getTotalPremiums(); + const startingLiquidity = await this.optionRoundFacade.getStartingLiquidity(); + const withdrawPremiumArgsAdjusted = []; + for (const args of withdrawPremiumArgs) { + const lockedBalance = await this.testRunner.vaultFacade.getLPLockedBalance(args.account.address); + const premiumsToWithdraw = (BigInt(lockedBalance) * BigInt(totalPremiums)) / + BigInt(startingLiquidity); + withdrawPremiumArgs.push({ + account: args.account, + amount: Math.floor(Number(premiumsToWithdraw)), + }); + } + await this.testRunner.withdrawAll(withdrawPremiumArgsAdjusted); + const lockedUnlockedBalances = await this.captureLockedUnlockedBalances(); + const vaultBalances = await this.captureVaultBalances(); + await this.optionRoundFacade.refundUnusedBidsAll(refundAllArgs); + const ethBalancesBidders = await this.captureEthBalancesOptionBidders(); + return { + lockedUnlockedBalances, + ethBalancesBidders, + vaultBalances, + }; + } + async simulateSettledState(exerciseOptionsArgs, withdrawalArgs, marketData) { + const data = await this.optionRoundFacade.optionRoundContract.get_state(); + await this.testRunner.settleOptionRoundBystander(marketData); + const withdrawArgsAdjusted = []; + for (const args of withdrawalArgs) { + const unlockedBalance = await this.testRunner.vaultFacade.getLPUnlockedBalance(args.account.address); + console.log("UNLOCKED", unlockedBalance); + withdrawArgsAdjusted.push({ + account: args.account, + amount: Math.floor(Number(args.amount) * Number(unlockedBalance)), + }); + } + const lpBefore = await this.captureLockedUnlockedBalances(); + await this.testRunner.withdrawAll(withdrawArgsAdjusted); + const lpAfter = await this.captureLockedUnlockedBalances(); + console.log("ARGS:", withdrawalArgs, "\nADjusted:", withdrawArgsAdjusted); + const lockedUnlockedBalances = await this.captureLockedUnlockedBalances(); + const vaultBalances = await this.captureVaultBalances(); + await this.optionRoundFacade.exerciseOptionsAll(exerciseOptionsArgs); + const ethBalancesBidders = await this.captureEthBalancesOptionBidders(); + return { + lockedUnlockedBalances, + ethBalancesBidders, + vaultBalances, + }; + } +} diff --git a/scripts/dist/scripts/utils/facades/TestRunner.js b/scripts/dist/scripts/utils/facades/TestRunner.js new file mode 100644 index 00000000..5679e1b7 --- /dev/null +++ b/scripts/dist/scripts/utils/facades/TestRunner.js @@ -0,0 +1,248 @@ +import { ERC20Facade } from "./erc20Facade"; +import { VaultFacade } from "./vaultFacade"; +import { getOptionRoundContract, getOptionRoundFacade } from "../helpers/setup"; +import { getNow, timeskipNextBlock } from "../katana"; +import { getAccount, getCustomAccount, stringToHex } from "../helpers/common"; +import { FactRegistryFacade } from "./factRegistryFacade"; +import { liquidityProviders, optionBidders } from "../constants"; +export class TestRunner { + provider; + ethFacade; + vaultFacade; + constants; + constructor(provider, vaultAddress, ethAddress, constants) { + this.vaultFacade = new VaultFacade(vaultAddress, provider); + this.ethFacade = new ERC20Facade(ethAddress, provider); + this.constants = constants; + this.provider = provider; + } + testResults = async (accounts, params, method) => { + const before = new Map(); + const after = new Map(); + const resultSheet = { + params, + accounts, + method, + before, + after, + }; + for (const param of params) { + switch (param) { + case StoragePoints.lpLocked: { + const res = await this.getLPLockedBalanceAll(accounts); + resultSheet.before.set(StoragePoints.lpLocked, res); + break; + } + case StoragePoints.lpUnlocked: { + const res = await this.getLPUnlockedBalanceAll(accounts); + resultSheet.before.set(StoragePoints.lpUnlocked, res); + break; + } + case StoragePoints.totalLocked: { + const res = await this.vaultFacade.getTotalLocked(); + resultSheet.before.set(StoragePoints.totalLocked, res); + break; + } + case StoragePoints.totalUnlocked: { + const res = await this.vaultFacade.getTotalUnLocked(); + resultSheet.before.set(StoragePoints.totalUnlocked, res); + break; + } + } + } + }; + getLPUnlockedBalanceAll = async (accounts) => { + const balances = await Promise.all(accounts.map(async (account) => { + const res = await this.vaultFacade.getLPUnlockedBalance(account.address); + return res; + })); + return balances; + }; + getLPLockedBalanceAll = async (accounts) => { + const balances = await Promise.all(accounts.map(async (account) => { + const res = await this.vaultFacade.getLPLockedBalance(account.address); + return res; + })); + return balances; + }; + depositAll = async (depositData) => { + for (const depositArgs of depositData) { + await this.vaultFacade.deposit(depositArgs); + } + }; + withdrawAll = async (withdrawData) => { + for (const withdrawArgs of withdrawData) { + await this.vaultFacade.withdraw(withdrawArgs); + } + }; + getBalancesAll = async (accounts) => { + const balances = await Promise.all(accounts.map(async (account) => { + const balance = await this.ethFacade.getBalance(account.address); + return balance; + })); + return balances; + }; + approveAll = async (approveData) => { + for (const approvalArgs of approveData) { + await this.ethFacade.approval(approvalArgs); + } + }; + accelerateToAuctioning = async () => { + const optionRoundContract = await getOptionRoundContract(this.provider, this.vaultFacade.vaultContract); + const currentTime = await getNow(this.provider); + const auctionStartDate = await optionRoundContract.get_auction_start_date(); + console.log("currentTime:", currentTime, "\nauctionStartDate:", auctionStartDate); + await timeskipNextBlock(Number(auctionStartDate) - Number(currentTime), this.provider.channel.nodeUrl); + }; + accelerateToRunning = async () => { + const optionRoundContract = await getOptionRoundContract(this.provider, this.vaultFacade.vaultContract); + const currentTime = await getNow(this.provider); + const auctionEndDate = await optionRoundContract.get_auction_end_date(); + await timeskipNextBlock(Number(auctionEndDate) - Number(currentTime) + 1, this.provider.channel.nodeUrl); + }; + accelerateToSettled = async () => { + const optionRoundContract = await getOptionRoundContract(this.provider, this.vaultFacade.vaultContract); + const currentTime = await getNow(this.provider); + const optionSettleDate = await optionRoundContract.get_option_settlement_date(); + await timeskipNextBlock(Number(optionSettleDate) - Number(currentTime), this.provider.channel.nodeUrl); + }; + //@note Only works for katana dev instance with a --dev flag + startAuctionBystander = async () => { + await this.accelerateToAuctioning(); + const devAccount = getAccount("dev", this.provider); + this.vaultFacade.vaultContract.connect(devAccount); + await this.vaultFacade.vaultContract.start_auction(); + // const settleDate = + // await optionRound.optionRoundContract.get_option_settlement_date(); + // + // const startDate = + // await optionRound.optionRoundContract.get_auction_start_date(); + // const twapPeriod = BigInt(60 * 60 * 24 * 14); + // + // const auctionRunTime = + // await this.vaultFacade.vaultContract.get_auction_run_time(); + // const optionRunTime = + // await this.vaultFacade.vaultContract.get_option_run_time(); + // const roundTransitionPeriod = + // await this.vaultFacade.vaultContract.get_round_transition_period(); + // + // const duration = + // BigInt(auctionRunTime) + + // BigInt(optionRunTime) + + // BigInt(roundTransitionPeriod); + // const endDatePeriodA = BigInt(startDate); + // const startDatePeriodA = endDatePeriodA - twapPeriod; + // + // const endDatePeriodB = endDatePeriodA - BigInt(roundTransitionPeriod); + // const startDatePeriodB = endDatePeriodB - twapPeriod; + // + // console.log("START DATE, SETTLE DATE", startDate, "\n", settleDate); + // const optionSettlementDate = parseInt( + // ( + // await optionRound.optionRoundContract.get_option_settlement_date() + // ).toString(), + // ); + // + // const twapRange = 3600 * 24 * 30; + // const volatilityRange = 3600 * 24 * 90; + // const reservePriceRange = 3600 * 24 * 90; + // + // const job_request: JobRequest = { + // identifiers: ["PITCH_LAKE_V1"], + // params: { + // twap: [ + // parseInt(optionSettlementDate.toString()) - twapRange, + // optionSettlementDate, + // ], + // volatility: [ + // optionSettlementDate - volatilityRange, + // optionSettlementDate, + // ], + // reserve_price: [ + // optionSettlementDate - reservePriceRange, + // optionSettlementDate, + // ], + // }, + // }; + // + // await factRegFacade.setMarketParameters({ + // devAccount, + // job_request, + // market_data: marketData, + // }); + // + // await this.vaultFacade.vaultContract.update_round_params(); + }; + endAuctionBystander = async () => { + const devAccount = getAccount("dev", this.provider); + await this.accelerateToRunning(); + await this.vaultFacade.endAuction(devAccount); + }; + //@note Only works for katana dev instance with a --dev flag + settleOptionRoundBystander = async (marketData) => { + console.log("MARKETDATA:", marketData); + const factRegistryString = await this.vaultFacade.vaultContract.get_market_aggregator_address(); + const factRegistryAddress = "0x" + stringToHex(factRegistryString); + const factRegFacade = new FactRegistryFacade(factRegistryAddress, this.provider); + const optionRound = await getOptionRoundFacade(this.provider, this.vaultFacade.vaultContract); + // Mock JobRequest + const jobRequest = await optionRound.createJobRequest(); + const devAccount = getAccount("dev", this.provider); + await factRegFacade.setMarketParameters({ + devAccount, + jobRequest, + marketData, + }); + await this.accelerateToSettled(); + await this.vaultFacade.settleOptionRound(devAccount, jobRequest); + // const optionSettlementDate = parseInt( + // ( + // await optionRound.optionRoundContract.get_option_settlement_date() + // ).toString(), + // ); + // const twapRange = 3600 * 24 * 30; + // const volatilityRange = 3600 * 24 * 90; + // const reservePriceRange = 3600 * 24 * 90; + // const job_request: JobRequest = { + // identifiers: ["PITCH_LAKE_V1"], + // params: { + // twap: [ + // parseInt(optionSettlementDate.toString()) - twapRange, + // optionSettlementDate, + // ], + // volatility: [ + // optionSettlementDate - volatilityRange, + // optionSettlementDate, + // ], + // reserve_price: [ + // optionSettlementDate - reservePriceRange, + // optionSettlementDate, + // ], + // }, + // }; + }; + getLiquidityProviderAccounts = (length) => { + const liquidityProviderAccounts = []; + for (let i = 0; i < length; i++) { + liquidityProviderAccounts.push(getCustomAccount(this.provider, liquidityProviders[i].account, liquidityProviders[i].privateKey)); + } + return liquidityProviderAccounts; + }; + getOptionBidderAccounts = (length) => { + const optionBidderAccounts = []; + for (let i = 0; i < length; i++) { + optionBidderAccounts.push(getCustomAccount(this.provider, optionBidders[i].account, optionBidders[i].privateKey)); + } + return optionBidderAccounts; + }; +} +var StoragePoints; +(function (StoragePoints) { + StoragePoints[StoragePoints["lpUnlocked"] = 0] = "lpUnlocked"; + StoragePoints[StoragePoints["lpLocked"] = 1] = "lpLocked"; + StoragePoints[StoragePoints["totalLocked"] = 2] = "totalLocked"; + StoragePoints[StoragePoints["totalUnlocked"] = 3] = "totalUnlocked"; +})(StoragePoints || (StoragePoints = {})); +var Methods; +(function (Methods) { +})(Methods || (Methods = {})); diff --git a/scripts/dist/scripts/utils/facades/erc20Facade.js b/scripts/dist/scripts/utils/facades/erc20Facade.js new file mode 100644 index 00000000..34e37487 --- /dev/null +++ b/scripts/dist/scripts/utils/facades/erc20Facade.js @@ -0,0 +1,71 @@ +import { CairoUint256, Contract, } from "starknet"; +import { erc20ABI } from "../../abi"; +import { getCustomAccount } from "../helpers/common"; +import { liquidityProviders, optionBidders } from "../constants"; +export class ERC20Facade { + erc20Contract; + constructor(ercAddress, provider) { + this.erc20Contract = new Contract(erc20ABI, ercAddress, provider).typedv2(erc20ABI); + } + async getBalance(account) { + const balance = await this.erc20Contract.balance_of(account); + //Parse U256 to CairoUint256 to BigInt + if (typeof balance !== "bigint" && typeof balance !== "number") { + const data = new CairoUint256(balance); + return data.toBigInt(); + } + else + return balance; + } + async supply(devAccount, recipient, amount) { + try { + this.erc20Contract.connect(devAccount); + await this.erc20Contract.transfer(recipient, amount); + // @note: don't delete it yet, waiting for response from starknet.js team + // const result = await account.execute({ + // contractAddress: ercContract, + // entrypoint: "transfer", + // calldata: CallData.compile({ + // recipient: liquidityProviders[0].account, + // amount: cairo.uint256(10000), + // }), + // }); + // const result2 = await provider.waitForTransaction(result.transaction_hash); + // console.log(result, result2); + } + catch (err) { + console.log(err); + } + } + async approval({ owner, amount, spender }) { + this.erc20Contract.connect(owner); + try { + await this.erc20Contract.approve(spender, amount); + } + catch (err) { + console.log(err); + } + } + async supplyERC20(devAccount, provider, erc20Address, approveFor) { + const erc20Contract = new Contract(erc20ABI, erc20Address, provider).typedv2(erc20ABI); + await this.supply(devAccount, "0x06Fb643e5c834feA33EACeFc10A2F856E1C317E700523c8eA45681F52D2B1D60", BigInt(10000000000)); + for (let i = 0; i < 6; i++) { + const lp = getCustomAccount(provider, liquidityProviders[i].account, liquidityProviders[i].privateKey); + const ob = getCustomAccount(provider, optionBidders[i].account, optionBidders[i].privateKey); + await this.supply(devAccount, liquidityProviders[i].account, BigInt("1000000000000000000")); + await this.approval({ + owner: lp, + amount: BigInt("1000000000000000000"), + spender: approveFor, + }); + console.log(`Liquidity Provider ${i} funded `); + await this.supply(devAccount, optionBidders[i].account, BigInt("1000000000000000000")); + await this.approval({ + owner: ob, + amount: BigInt("1000000000000000000"), + spender: approveFor, + }); + console.log(`Option Bidder ${i} funded `); + } + } +} diff --git a/scripts/dist/scripts/utils/facades/factRegistryFacade.js b/scripts/dist/scripts/utils/facades/factRegistryFacade.js new file mode 100644 index 00000000..5ed5560a --- /dev/null +++ b/scripts/dist/scripts/utils/facades/factRegistryFacade.js @@ -0,0 +1,104 @@ +import { Contract } from "starknet"; +import { factRegistryABI } from "../../abi"; +export class FactRegistryFacade { + factRegistryContract; + constructor(factRegistryAddress, provider) { + this.factRegistryContract = new Contract(factRegistryABI, factRegistryAddress, provider).typedv2(factRegistryABI); + } + async setFact(account, job_request, market_data) { + this.factRegistryContract.connect(account); + await this.factRegistryContract.set_fact(job_request, [ + market_data.settlementPrice, + market_data.volatility, + market_data.reservePrice, + ]); + } + // async setVolatility( + // account: Account, + // vaultAddress: string, + // roundId: number | bigint, + // volatility: number | bigint, + // ) { + // this.marketAggregatorContract.connect(account); + // await this.marketAggregatorContract.set_volatility_for_round( + // vaultAddress, + // roundId, + // volatility, + // ); + // } + // async setCapLevel( + // account: Account, + // vaultAddress: string, + // roundId: number | bigint, + // capLevel: number | bigint, + // ) { + // this.marketAggregatorContract.connect(account); + // await this.marketAggregatorContract.set_cap_level_for_round( + // vaultAddress, + // roundId, + // capLevel, + // ); + // } + // async setReservePrice( + // account: Account, + // vaultAddress: string, + // roundId: bigint | number, + // reservePrice: number | bigint, + // ) { + // this.marketAggregatorContract.connect(account); + // await this.marketAggregatorContract.set_reserve_price_for_round( + // vaultAddress, + // roundId, + // reservePrice, + // ); + // } + // async setTWAP( + // account: Account, + // from: number | bigint, + // to: number | bigint, + // reservePrice: number | bigint, + // ) { + // this.marketAggregatorContract.connect(account); + // await this.marketAggregatorContract.set_TWAP_for_time_period( + // from, + // to, + // reservePrice, + // ); + // } + async setMarketParameters({ devAccount, jobRequest, marketData, }) { + // job_request and data + await this.setFact(devAccount, jobRequest, marketData); + // await this.setReservePrice( + // devAccount, + // vaultAddress, + // roundId, + // marketData.reservePrice, + // ); + // await this.setCapLevel( + // devAccount, + // vaultAddress, + // roundId, + // marketData.capLevel, + // ); + // + // await this.setVolatility( + // devAccount, + // vaultAddress, + // roundId, + // marketData.capLevel, + // ); + // + // await this.setTWAP( + // devAccount, + // startDatePeriodA, + // endDatePeriodA, + // marketData.settlementPrice, + // ); + // await this.setTWAP( + // devAccount, + // startDatePeriodB, + // endDatePeriodB, + // marketData.settlementPrice, + // ); + } +} diff --git a/scripts/dist/scripts/utils/facades/optionRoundFacade.js b/scripts/dist/scripts/utils/facades/optionRoundFacade.js new file mode 100644 index 00000000..f416023d --- /dev/null +++ b/scripts/dist/scripts/utils/facades/optionRoundFacade.js @@ -0,0 +1,202 @@ +import { CairoUint256 } from "starknet"; +import { convertToBigInt } from "../helpers/common"; +export class OptionRoundFacade { + optionRoundContract; + constructor(optionRoundContract) { + this.optionRoundContract = optionRoundContract; + } + async createJobRequest() { + const state = await this.optionRoundContract.get_state(); + const upperBound = state && Object.keys(state)[0] === "Open" + ? Number(await this.optionRoundContract.get_auction_start_date()) + : Number(await this.optionRoundContract.get_option_settlement_date()); + const DAY = 24 * 3600; + const job_request = { + identifiers: ["PITCH_LAKE_V1"], + params: { + twap: [upperBound - 30 * DAY, upperBound], + volatility: [upperBound - 90 * DAY, upperBound], + reserve_price: [upperBound - 90 * DAY, upperBound], + }, + }; + return job_request; + // + } + async getStartingLiquidity() { + const res = await this.optionRoundContract.get_starting_liquidity(); + return convertToBigInt(res); + } + async getRoundId() { + const res = await this.optionRoundContract.get_round_id(); + return convertToBigInt(res); + } + async getTotalPayout() { + const res = await this.optionRoundContract.get_total_payout(); + return convertToBigInt(res); + } + async getTotalPremiums() { + const res = await this.optionRoundContract.get_total_premium(); + return convertToBigInt(res); + } + async getTotalOptionsAvailable() { + const res = await this.optionRoundContract.get_options_available(); + return convertToBigInt(res); + } + async getReservePrice() { + const res = await this.optionRoundContract.get_reserve_price(); + return convertToBigInt(res); + } + async getBidsFor(address) { + const res = await this.optionRoundContract.get_account_bids(address); + const bids = []; + for (let data of res) { + let amount; + let price; + if (typeof data.amount !== "bigint" && typeof data.amount !== "number") { + const res = new CairoUint256(data.amount); + amount = res.toBigInt(); + } + else { + amount = data.amount; + } + if (typeof data.price !== "bigint" && typeof data.price !== "number") { + const res = new CairoUint256(data.price); + price = res.toBigInt(); + } + else { + price = data.price; + } + const bid = { + id: data.bid_id, + amount: amount, + nonce: data.tree_nonce, + owner: data.owner, + price: price, + }; + bids.push(bid); + } + return bids; + } + async getBidsForAll(accounts) { + const bids = await Promise.all(accounts.map(async (account) => { + const bidData = await this.getBidsFor(account.address); + return bidData; + })); + return bids; + } + async updateBid({ bidId, from, amount, price }) { + this.optionRoundContract.connect(from); + try { + await this.optionRoundContract.update_bid(bidId, amount, price); + } + catch (err) { + console.log(err); + } + } + async placeBid({ from, amount, price }) { + this.optionRoundContract.connect(from); + try { + const data = await this.optionRoundContract.place_bid(amount, price); + } + catch (err) { + const error = err; + console.log(error.name, from, amount, price, error.message, error.cause); + } + } + async placeBidsAll(placeBidData) { + for (const placeBidArgs of placeBidData) { + await this.placeBid(placeBidArgs); + } + } + async getRefundableBidsFor({ optionBuyer }) { + try { + const res = await this.optionRoundContract.get_refundable_bids_for(optionBuyer); + return convertToBigInt(res); + } + catch (err) { + console.log(err); + } + } + async getTotalOptionsBalanceForAll(optionBuyers) { + try { + const optionsBalances = await Promise.all(optionBuyers.map(async (account) => { + const balance = await this.getTotalOptionsBalanceFor(account.address); + return balance; + })); + return optionsBalances; + } + catch (err) { + console.log(err); + } + } + async getTotalOptionsBalanceFor(optionBuyer) { + try { + const res = await this.optionRoundContract.get_account_total_options(optionBuyer); + return convertToBigInt(res); + } + catch (err) { + console.log(err); + } + } + async getPayoutBalanceFor({ optionBuyer }) { + try { + const res = await this.optionRoundContract.get_payout_balance_for(optionBuyer); + return convertToBigInt(res); + } + catch (err) { + console.log(err); + } + } + async getTokenizableOptionsFor({ optionBuyer }) { + try { + const res = await this.optionRoundContract.get_tokenizable_options_for(optionBuyer); + return convertToBigInt(res); + } + catch (err) { + console.log(err); + } + } + async refundUnusedBids({ from, optionBidder }) { + this.optionRoundContract.connect(from); + const data = await this.optionRoundContract.refund_unused_bids(optionBidder); + console.log("refund unused bids inside -> ", data); + // @note: here it will return the total refundable_balance + // if (typeof res !== "bigint" && typeof res !== "number") { + // const data = new CairoUint256(res); + // return data.toBigInt(); + // } else return res;s + } + async refundUnusedBidsAll(refundUnusedBidsAllArgs) { + for (const refundArgs of refundUnusedBidsAllArgs) { + await this.refundUnusedBids(refundArgs); + } + } + async exerciseOptions({ from }) { + this.optionRoundContract.connect(from); + const data = await this.optionRoundContract.exercise_options(); + // @note: here it will return the amount of transfer + // if (typeof res !== "bigint" && typeof res !== "number") { + // const data = new CairoUint256(res); + // return data.toBigInt(); + // } else return res; + } + async exerciseOptionsAll(exerciseOptionData) { + for (const exerciseOptionsArgs of exerciseOptionData) { + await this.exerciseOptions(exerciseOptionsArgs); + } + } + async tokenizeOptions({ from }) { + try { + this.optionRoundContract.connect(from); + const data = await this.optionRoundContract.mint_options(); + // @note: here it will return the total number of tokenizable options + // if (typeof res !== "bigint" && typeof res !== "number") { + // const data = new CairoUint256(res); + // return data.toBigInt(); + // } else return res; + } + catch (err) { + console.log(err); + } + } +} diff --git a/scripts/dist/scripts/utils/facades/types.js b/scripts/dist/scripts/utils/facades/types.js new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/scripts/dist/scripts/utils/facades/types.js @@ -0,0 +1 @@ +export {}; diff --git a/scripts/dist/scripts/utils/facades/vaultFacade.js b/scripts/dist/scripts/utils/facades/vaultFacade.js new file mode 100644 index 00000000..fefaf005 --- /dev/null +++ b/scripts/dist/scripts/utils/facades/vaultFacade.js @@ -0,0 +1,55 @@ +import { Contract } from "starknet"; +import { optionRoundABI, vaultABI } from "../../abi"; +import { convertToBigInt } from "../helpers/common"; +export class VaultFacade { + vaultContract; + currentOptionRound; + constructor(vaultAddress, provider, optionRoundAddress) { + this.vaultContract = new Contract(vaultABI, vaultAddress, provider).typedv2(vaultABI); + if (optionRoundAddress) + this.currentOptionRound = new Contract(optionRoundABI, optionRoundAddress, provider).typedv2(optionRoundABI); + } + async getTotalLocked() { + const res = await this.vaultContract.get_vault_locked_balance(); + return convertToBigInt(res); + } + async getTotalUnLocked() { + const res = await this.vaultContract.get_vault_unlocked_balance(); + return convertToBigInt(res); + } + async getLPLockedBalance(address) { + const res = await this.vaultContract.get_account_locked_balance(address); + return convertToBigInt(res); + } + async getLPUnlockedBalance(address) { + const res = await this.vaultContract.get_account_unlocked_balance(address); + return convertToBigInt(res); + } + async withdraw({ account, amount }) { + this.vaultContract.connect(account); + await this.vaultContract.withdraw(amount); + } + async deposit({ from, beneficiary, amount }) { + this.vaultContract.connect(from); + try { + const data = await this.vaultContract.deposit(amount, beneficiary); + data; + } + catch (err) { + console.log(err); + } + } + //State Transitions + async startAuction(account) { + this.vaultContract.connect(account); + await this.vaultContract.start_auction(); + } + async endAuction(account) { + this.vaultContract.connect(account); + await this.vaultContract.end_auction(); + } + async settleOptionRound(account, job_request) { + this.vaultContract.connect(account); + await this.vaultContract.settle_round(job_request); + } +} diff --git a/scripts/dist/scripts/utils/helpers/common.js b/scripts/dist/scripts/utils/helpers/common.js new file mode 100644 index 00000000..3f166658 --- /dev/null +++ b/scripts/dist/scripts/utils/helpers/common.js @@ -0,0 +1,54 @@ +import { Account, CairoUint256, Contract, RpcProvider } from "starknet"; +import { nodeUrlMapping, accountDetailsMapping } from "../constants"; +function getProvider(environment, port) { + const nodeUrl = nodeUrlMapping[environment] + `${port ? `:${port}` : ""}`; + if (environment === "dev" && port === null) { + throw new Error("Port must be provided for dev environment"); + } + if (!nodeUrl) { + throw new Error("Invalid environment"); + } + const provider = new RpcProvider({ + nodeUrl: nodeUrl, + }); + return provider; +} +function getAccount(environment, provider) { + const accountDetails = accountDetailsMapping[environment]; + if (!accountDetails || + !accountDetails.accountAddress || + !accountDetails.privateKey) { + throw new Error("Invalid environment or missing account details in environment variables"); + } + const account = new Account(provider, accountDetails.accountAddress, accountDetails.privateKey); + return account; +} +function getCustomAccount(provider, accountAddress, privateKey) { + if (!accountAddress || !privateKey) { + throw new Error("Invalid or missing account details"); + } + const account = new Account(provider, accountAddress, privateKey); + return account; +} +async function getContract(provider, account, contractAddress) { + const { abi: contractAbi } = await provider.getClassAt(contractAddress); + if (contractAbi === undefined) { + throw new Error("No ABI."); + } + const contract = new Contract(contractAbi, contractAddress, provider); + contract.connect(account); + return contract; +} +function stringToHex(decimalString) { + decimalString = String(decimalString); + const num = BigInt(decimalString); + return num.toString(16); +} +function convertToBigInt(quantity) { + if (typeof quantity !== "bigint" && typeof quantity !== "number") { + const res = new CairoUint256(quantity); + quantity = res.toBigInt(); + } + return quantity; +} +export { getProvider, getAccount, getContract, getCustomAccount, stringToHex, convertToBigInt }; diff --git a/scripts/dist/scripts/utils/helpers/setup.js b/scripts/dist/scripts/utils/helpers/setup.js new file mode 100644 index 00000000..79b1aa26 --- /dev/null +++ b/scripts/dist/scripts/utils/helpers/setup.js @@ -0,0 +1,95 @@ +import { CairoUint256, Contract } from "starknet"; +import { stringToHex } from "./common"; +import { erc20ABI, optionRoundABI } from "../../abi"; +import { OptionRoundFacade } from "../facades/optionRoundFacade"; +export const getOptionRoundFacade = async (provider, vault, prev) => { + const optionRoundContract = await getOptionRoundContract(provider, vault, prev); + const optionRoundFacade = new OptionRoundFacade(optionRoundContract); + return optionRoundFacade; +}; +export const getOptionRoundERC20Contract = async (provider, optionRound) => { + // const optionRoundContract = await getOptionRoundContract(provider, vault); + const optionRoundERC20Contract = new Contract(erc20ABI, optionRound.address, provider).typedv2(erc20ABI); + return optionRoundERC20Contract; +}; +export const getOptionRoundContract = async (provider, vault, prev) => { + let optionRoundId = await vault.get_current_round_id(); + let id; + if (typeof optionRoundId !== "number" && typeof optionRoundId !== "bigint") { + const temp = new CairoUint256(optionRoundId); + id = temp.toBigInt(); + } + else + id = BigInt(optionRoundId); + if (prev) { + id = id - BigInt(1); + } + const optionRoundAddressDecimalString = await vault.get_round_address(id); + const optionRoundAddress = "0x" + stringToHex(optionRoundAddressDecimalString); + const optionRoundContract = new Contract(optionRoundABI, optionRoundAddress, provider).typedv2(optionRoundABI); + return optionRoundContract; +}; +export const generateSimulationParams = ({ getLiquidityProviderAccounts, getOptionBidderAccounts }, simulationSheets) => { + const liquidityProviderAccounts = getLiquidityProviderAccounts(5); + const optionBidderAccounts = getOptionBidderAccounts(5); + const simulationParams = simulationSheets.map((simulationSheet) => { + const depositAllArgs = simulationSheet.liquidityProviders.map((provider, index) => { + return { + from: liquidityProviderAccounts[provider - 1], + beneficiary: liquidityProviderAccounts[provider - 1].address, + amount: simulationSheet.depositAmounts[index], + }; + }); + const bidAllArgs = simulationSheet.optionBidders.map((bidder, index) => { + const data = { + from: optionBidderAccounts[bidder - 1], + amount: Number(simulationSheet.bidAmounts[index]), + price: BigInt(simulationSheet.bidPrices[index]), + }; + return data; + }); + let ref = {}; + const refundAllArgs = []; + bidAllArgs.map((bids) => { + if (!ref[bids.from.address]) { + ref[bids.from.address] = true; + refundAllArgs.push({ + from: bids.from, + optionBidder: bids.from.address, + }); + } + }); + let withdrawPremiumArgs = []; + if (simulationSheet.withdrawalsPremium) { + withdrawPremiumArgs = simulationSheet.withdrawalsPremium.map((bidder) => ({ + account: liquidityProviderAccounts[bidder - 1], + amount: 0, + })); + } + let withdrawalArgs = []; + if (simulationSheet.withdrawalAmounts && simulationSheet.withdrawals) { + withdrawalArgs = simulationSheet.withdrawals.map((bidder, index) => { + return { + account: liquidityProviderAccounts[bidder - 1], + amount: Number(simulationSheet.withdrawalAmounts + ? simulationSheet.withdrawalAmounts[index] + : 0), + }; + }); + } + const exerciseOptionsAllArgs = simulationSheet.optionBidders.map((bidder) => ({ + from: optionBidderAccounts[bidder - 1], + })); + const data = { + depositAllArgs, + bidAllArgs, + marketData: simulationSheet.marketData, + withdrawPremiumArgs, + withdrawalArgs, + exerciseOptionsAllArgs, + refundAllArgs, + }; + return data; + }); + return simulationParams; +}; diff --git a/scripts/dist/scripts/utils/katana.js b/scripts/dist/scripts/utils/katana.js new file mode 100644 index 00000000..565a4df2 --- /dev/null +++ b/scripts/dist/scripts/utils/katana.js @@ -0,0 +1,42 @@ +export const setNextBlock = async (increase, url) => { + const options = { + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, + body: JSON.stringify({ + id: 1, + jsonrpc: "2.0", + method: "dev_increaseNextBlockTimestamp", + params: [increase], + }), + }; + await fetch(url, options) + .then((response) => response.json()) + .then((response) => { }) + .catch((err) => console.error(err)); +}; +export const mineNextBlock = async (url) => { + const options = { + method: "POST", + headers: { accept: "application/json", "content-type": "application/json" }, + body: JSON.stringify({ + id: 1, + jsonrpc: "2.0", + method: "dev_generateBlock", + params: [], + }), + }; + await fetch(url, options) + .then((response) => response.json()) + .then((response) => { }) + .catch((err) => console.error(err)); +}; +export const timeskipNextBlock = async (increaseTime, url) => { + await mineNextBlock(url); + if (increaseTime > 0) + await setNextBlock(increaseTime, url); + await mineNextBlock(url); +}; +export const getNow = async (provider) => { + const data = await provider.getBlock(); + return data.timestamp; +}; diff --git a/scripts/generate_abi.sh b/scripts/generate_abi.sh index e6564666..eda62741 100755 --- a/scripts/generate_abi.sh +++ b/scripts/generate_abi.sh @@ -3,10 +3,10 @@ # Define contracts and their file paths # Add contracts here as "contract_name:abi_name" contracts=( - "OptionRound:optionRound" - "Vault:vault" - "MarketAggregator:marketAggregator" - "Eth:eth" + "OptionRound:optionRound" + "Vault:vault" + "FactRegistry:factRegistry" + "Eth:eth" ) echo "Running scarb build..." @@ -14,9 +14,9 @@ cd .. && scarb build && cd scripts # Generate ABIs for contract in "${contracts[@]}"; do - IFS=':' read -r contract_name abi_name <<< "$contract" - json_file="../target/dev/pitch_lake_starknet_${contract_name}.contract_class.json" - abi_file="./abi/${abi_name}.ts" - - npx abi-wan-kanabi --input "$json_file" --output "$abi_file" + IFS=':' read -r contract_name abi_name <<<"$contract" + json_file="../target/dev/pitch_lake_${contract_name}.contract_class.json" + abi_file="./abi/${abi_name}.ts" + + npx abi-wan-kanabi --input "$json_file" --output "$abi_file" done diff --git a/scripts/integrationTests/smokeTest1/auctionStart.ts b/scripts/integrationTests/smokeTest1/auctionStart.ts index e51bd8c7..51bf4385 100644 --- a/scripts/integrationTests/smokeTest1/auctionStart.ts +++ b/scripts/integrationTests/smokeTest1/auctionStart.ts @@ -2,10 +2,7 @@ import { LibraryError, Uint256 } from "starknet"; import { getAccount } from "../../utils/helpers/common"; import { getOptionRoundFacade } from "../../utils/helpers/setup"; import assert from "assert"; -import { - ApprovalArgs, - PlaceBidArgs, -} from "../../utils/facades/types"; +import { ApprovalArgs, PlaceBidArgs } from "../../utils/facades/types"; import { mineNextBlock } from "../../utils/katana"; import { TestRunner } from "../../utils/facades/TestRunner"; @@ -19,11 +16,11 @@ export const smokeTest = async ({ approveAll, startAuctionBystander, getLiquidityProviderAccounts, - getOptionBidderAccounts + getOptionBidderAccounts, }: TestRunner) => { const optionRoundFacade = await getOptionRoundFacade( provider, - vaultFacade.vaultContract + vaultFacade.vaultContract, ); const devAccount = getAccount("dev", provider); try { @@ -43,17 +40,15 @@ export const smokeTest = async ({ assert( stateAfter.activeVariant() === "Open", - `Expected:Open\nReceived:${stateAfter.activeVariant()}` + `Expected:Open\nReceived:${stateAfter.activeVariant()}`, ); - await startAuctionBystander(constants); + await startAuctionBystander(); const unlockedBalances = await getLPUnlockedBalanceAll( - liquidityProviderAccounts - ); - const lockedBalances = await getLPLockedBalanceAll( - liquidityProviderAccounts + liquidityProviderAccounts, ); + const lockedBalances = await getLPLockedBalanceAll(liquidityProviderAccounts); const totalLockedAmount = await vaultFacade.getTotalLocked(); const totalUnlockedAmount = await vaultFacade.getTotalUnLocked(); @@ -88,9 +83,7 @@ export const smokeTest = async ({ const totalOptionAvailable = await optionRoundFacade.getTotalOptionsAvailable(); - const ethBalancesBefore = await getBalancesAll( - optionBidderAccounts - ); + const ethBalancesBefore = await getBalancesAll(optionBidderAccounts); const placeBidsData: Array = [ { @@ -139,33 +132,33 @@ async function checkpoint1({ }) { assert( Number(unlockedBalances[0]) === 0, - `UnlockedBalanceA 0 expected, found ${unlockedBalances[0]}` + `UnlockedBalanceA 0 expected, found ${unlockedBalances[0]}`, ); assert( Number(unlockedBalances[1]) === 0, - `UnlockedBalanceB 0 expected, found ${unlockedBalances[1]}` + `UnlockedBalanceB 0 expected, found ${unlockedBalances[1]}`, ); assert( BigInt(lockedBalances[0]) === BigInt(depositAmount) / BigInt(2), `LockedBalanceA ${BigInt(depositAmount) / BigInt(2)} expected, found ${ lockedBalances[0] - }` + }`, ); assert( BigInt(lockedBalances[1]) === BigInt(depositAmount) / BigInt(2), `LockedBalanceB ${BigInt(depositAmount) / BigInt(2)} expected, found ${ lockedBalances[1] - }` + }`, ); assert( BigInt(totalUnlockedAmount) === BigInt(0), - `Total unlocked 0 expected, found ${totalUnlockedAmount}` + `Total unlocked 0 expected, found ${totalUnlockedAmount}`, ); assert( BigInt(totalLockedAmount) === BigInt(depositAmount), `Total Locked amount ${BigInt( - depositAmount - )} expected, found ${totalLockedAmount}` + depositAmount, + )} expected, found ${totalLockedAmount}`, ); } @@ -187,39 +180,42 @@ async function checkpoint2({ BigInt(ethBalancesBefore[0]) - BigInt(ethBalancesAfter[0]) === (BigInt(3) * BigInt(reservePrice) * BigInt(totalOptionAvailable)) / BigInt(2), - "Error A" + "Error A", ); assert( BigInt(ethBalancesBefore[1]) - BigInt(ethBalancesAfter[1]) === (BigInt(3) * BigInt(reservePrice) * BigInt(totalOptionAvailable)) / BigInt(2), - "Error B" + "Error B", ); - assert(bidArrays[0].length === 1, `No. of Bids for A wrong,\n Expected:${BigInt(totalOptionAvailable)}\n Received:${bidArrays[0]?.length}`); + assert( + bidArrays[0].length === 1, + `No. of Bids for A wrong,\n Expected:${BigInt(totalOptionAvailable)}\n Received:${bidArrays[0]?.length}`, + ); assert( bidArrays[0][0].amount === BigInt(totalOptionAvailable) / BigInt(2), - "Bid for A amount wrong" + "Bid for A amount wrong", ); assert( bidArrays[0][0].price === BigInt(3) * BigInt(reservePrice), - "Bid for A price wrong" + "Bid for A price wrong", ); assert(bidArrays[1].length === 2, "No. of Bids for B wrong"); assert( bidArrays[1][0].amount === BigInt(totalOptionAvailable) / BigInt(2), - "First bid for B amount wrong" + "First bid for B amount wrong", ); assert( bidArrays[1][0].price === BigInt(2) * BigInt(reservePrice), - "First bid for B price wrong" + "First bid for B price wrong", ); assert( bidArrays[1][1].amount === BigInt(totalOptionAvailable) / BigInt(2), - "Second bid for B amount wrong " + "Second bid for B amount wrong ", ); assert( BigInt(bidArrays[1][1].price) === BigInt(reservePrice), - `Second bid for B price wrong.\n Expected:${reservePrice}, Actual:${bidArrays[1][0].price}` + `Second bid for B price wrong.\n Expected:${reservePrice}, Actual:${bidArrays[1][0].price}`, ); } diff --git a/scripts/integrationTests/smokeTest1/optionSettle.ts b/scripts/integrationTests/smokeTest1/optionSettle.ts index 45a3a47a..5f189361 100644 --- a/scripts/integrationTests/smokeTest1/optionSettle.ts +++ b/scripts/integrationTests/smokeTest1/optionSettle.ts @@ -7,7 +7,7 @@ import { eth, LibraryError } from "starknet"; export const smokeTest = async ({ provider, vaultFacade, - constants: { depositAmount }, + constants: { depositAmount, settlementPrice, volatility, reservePrice }, ethFacade, getLPUnlockedBalanceAll, settleOptionRoundBystander, @@ -16,7 +16,7 @@ export const smokeTest = async ({ }: TestRunner) => { const optionRoundFacade = await getOptionRoundFacade( provider, - vaultFacade.vaultContract + vaultFacade.vaultContract, ); const liquidityProviderAccounts = getLiquidityProviderAccounts(2); @@ -24,7 +24,8 @@ export const smokeTest = async ({ const devAccount = getAccount("dev", provider); try { - await vaultFacade.settleOptionRound(devAccount); + let jobRequest = await optionRoundFacade.createJobRequest(); + await vaultFacade.settleOptionRound(devAccount, jobRequest); throw Error("Should have reverted"); } catch (err) { const error = err as LibraryError; @@ -36,12 +37,12 @@ export const smokeTest = async ({ assert( state.activeVariant() === "Running", - `Expected:Running\nReceived:${state.activeVariant()}` + `Expected:Running\nReceived:${state.activeVariant()}`, ); const totalPremiums = await optionRoundFacade.getTotalPremiums(); const ethBalanceBefore = await ethFacade.getBalance( - liquidityProviderAccounts[0].address + liquidityProviderAccounts[0].address, ); await vaultFacade.withdraw({ @@ -50,10 +51,10 @@ export const smokeTest = async ({ }); const ethBalanceAfter = await ethFacade.getBalance( - liquidityProviderAccounts[0].address + liquidityProviderAccounts[0].address, ); const lpUnlockedBalanceAfter = await vaultFacade.getLPLockedBalance( - liquidityProviderAccounts[0].address + liquidityProviderAccounts[0].address, ); const totalUnlocked = await vaultFacade.getTotalUnLocked(); @@ -66,21 +67,27 @@ export const smokeTest = async ({ totalPremiums, }); - await settleOptionRoundBystander(); + const marketData = { + settlementPrice, + volatility, + reservePrice, + }; + + await settleOptionRoundBystander(marketData); const stateAfter: any = await optionRoundFacade.optionRoundContract.get_state(); const lpUnlockedBalances = await getLPUnlockedBalanceAll( - liquidityProviderAccounts + liquidityProviderAccounts, ); const totalPayout = await optionRoundFacade.getTotalPayout(); const totalLocked = await vaultFacade.getTotalLocked(); const totalUnlockedAfter = await vaultFacade.getTotalUnLocked(); - + assert( stateAfter.activeVariant() === "Settled", - `Expected:Running\nReceived:${stateAfter.activeVariant()}` + `Expected:Running\nReceived:${stateAfter.activeVariant()}`, ); checkpoint2({ @@ -88,7 +95,7 @@ export const smokeTest = async ({ totalPremiums, totalPayout, totalLocked, - totalUnlocked:totalUnlockedAfter, + totalUnlocked: totalUnlockedAfter, depositAmount, }); }; @@ -114,20 +121,20 @@ async function checkpoint1({ "\ntotalPremiums", totalPremiums, "lpUnlockedBalanceAfter:", - lpUnlockedBalanceAfter + lpUnlockedBalanceAfter, ); assert( BigInt(ethBalanceAfter) - BigInt(ethBalanceBefore) === BigInt(totalPremiums) / BigInt(4), - "LP Unlocked for A mismatch" + "LP Unlocked for A mismatch", ); assert( BigInt(lpUnlockedBalanceAfter) === BigInt(totalPremiums) / BigInt(4), - "LP Unlocked for B mismatch" + "LP Unlocked for B mismatch", ); assert( BigInt(totalUnlocked) === (BigInt(3) * BigInt(totalPremiums)) / BigInt(4), - "LP Locked for A mismatch" + "LP Locked for A mismatch", ); } @@ -153,7 +160,7 @@ async function checkpoint2({ BigInt(depositAmount) / BigInt(2) + BigInt(totalPremiums) / BigInt(4) - BigInt(totalPayout) / BigInt(2), - "lpUnlocked for A Mismatch" + "lpUnlocked for A Mismatch", ); // - B’s unlocked balance should be 1/2 deposit amount + 1/2 total premiums - 1/2 total payout @@ -163,13 +170,13 @@ async function checkpoint2({ BigInt(depositAmount) / BigInt(2) + BigInt(totalPremiums) / BigInt(2) - BigInt(totalPayout) / BigInt(2), - "lpUnlocked for B Mismatch" + "lpUnlocked for B Mismatch", ); // - The total locked should == 0 assert( Number(totalLocked) === 0, - `total Locked should be zero, found: ${totalLocked}` + `total Locked should be zero, found: ${totalLocked}`, ); // - The total unlocked should == deposit amount + 3/4 total premiums - total payout @@ -178,6 +185,6 @@ async function checkpoint2({ BigInt(depositAmount) + (BigInt(3) * BigInt(totalPremiums)) / BigInt(4) - BigInt(totalPayout), - `totalUnlocked mismatch \n${totalUnlocked}\n${lpUnlockedBalances}` + `totalUnlocked mismatch \n${totalUnlocked}\n${lpUnlockedBalances}`, ); } diff --git a/scripts/package-lock.json b/scripts/package-lock.json index 30471ac2..2d81bae7 100644 --- a/scripts/package-lock.json +++ b/scripts/package-lock.json @@ -14,7 +14,7 @@ "ts-node": "^10.9.2" }, "devDependencies": { - "typescript": "^5.5.3" + "typescript": "^5.6.2" } }, "node_modules/@cspotcode/source-map-support": { @@ -580,9 +580,9 @@ } }, "node_modules/typescript": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", - "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/scripts/package.json b/scripts/package.json index 7af1f632..fd4d4acb 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "", "main": "index.js", - "type":"module", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, @@ -16,6 +16,6 @@ "ts-node": "^10.9.2" }, "devDependencies": { - "typescript": "^5.5.3" + "typescript": "^5.6.2" } } diff --git a/scripts/simulationData/marketData.json b/scripts/simulationData/marketData.json index b898933c..b36f2d4b 100644 --- a/scripts/simulationData/marketData.json +++ b/scripts/simulationData/marketData.json @@ -5,8 +5,7 @@ "reserve_price": 1133806091.8973722, "strike_price": 17635296321.331875, "settlement_price": 17635296321.87111, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1673760608, @@ -14,8 +13,7 @@ "reserve_price": 2289497207.1168942, "strike_price": 21386087366.87111, "settlement_price": 21386087366.817963, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1676439005, @@ -23,8 +21,7 @@ "reserve_price": 1279595660.5008752, "strike_price": 29034664790.817963, "settlement_price": 29034664790.02064, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1678858200, @@ -32,8 +29,7 @@ "reserve_price": 875665408.7313896, "strike_price": 38493145481.02064, "settlement_price": 38493145481.663445, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1681536603, @@ -41,8 +37,7 @@ "reserve_price": 2633366765.449701, "strike_price": 25439135433.663445, "settlement_price": 25439135433.31648, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1684128611, @@ -50,8 +45,7 @@ "reserve_price": 1356220491.835023, "strike_price": 74555216167.31648, "settlement_price": 74555216167.505894, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1686807007, @@ -59,8 +53,7 @@ "reserve_price": 1952777064.7434845, "strike_price": 20136894952.505894, "settlement_price": 20136894952.938293, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1689399000, @@ -68,8 +61,7 @@ "reserve_price": 2146702361.445386, "strike_price": 22828235819.938293, "settlement_price": 22828235819.848763, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1692077411, @@ -77,8 +69,7 @@ "reserve_price": 1644783441.6741364, "strike_price": 19320316991.848763, "settlement_price": 19320316991.733692, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1694755801, @@ -86,8 +77,7 @@ "reserve_price": 1467840115.5542483, "strike_price": 15696616976.733692, "settlement_price": 15696616976.569965, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1697347808, @@ -95,8 +85,7 @@ "reserve_price": 554611569.0124147, "strike_price": 7419675169.569965, "settlement_price": 7419675169.247475, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1700026208, @@ -104,8 +93,7 @@ "reserve_price": 3834880492.891962, "strike_price": 37644135519.247475, "settlement_price": 37644135519.43376, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1702618206, @@ -113,8 +101,7 @@ "reserve_price": 5010998700.134231, "strike_price": 39788123292.43376, "settlement_price": 39788123292.067184, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1705296605, @@ -122,8 +109,7 @@ "reserve_price": 2102480779.8673804, "strike_price": 26352409279.067184, "settlement_price": 26352409279.14552, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1707975009, @@ -131,8 +117,7 @@ "reserve_price": 1358716570.77586, "strike_price": 40189317122.14552, "settlement_price": 40189317122.033165, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1710480615, @@ -140,8 +125,7 @@ "reserve_price": 7431323768.285521, "strike_price": 58500408944.033165, "settlement_price": 58500408944.007843, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1713159006, @@ -149,8 +133,7 @@ "reserve_price": 1105329970.4182699, "strike_price": 24358911017.007843, "settlement_price": 24358911017.434905, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1715751017, @@ -158,8 +141,7 @@ "reserve_price": 517729381.0134145, "strike_price": 5681275902.434905, "settlement_price": 5681275902.398533, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1718429397, @@ -167,8 +149,7 @@ "reserve_price": 1008885496.8860922, "strike_price": 10706888590.398533, "settlement_price": 10706888590.4517756, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1721021409, @@ -176,8 +157,7 @@ "reserve_price": 347501891.0070594, "strike_price": 3907948839.4517756, "settlement_price": 3907948839.922286, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 }, { "starting_timestamp": 1721160731, @@ -185,7 +165,6 @@ "reserve_price": 361402055.1651912, "strike_price": 5191304671.922286, "settlement_price": 5191304671.922286, - "cap_level": 3000, - "volatility": 10 + "volatility": 3000 } ] diff --git a/scripts/simulationTests/index.ts b/scripts/simulationTests/index.ts index 61840738..79cbea2f 100644 --- a/scripts/simulationTests/index.ts +++ b/scripts/simulationTests/index.ts @@ -19,7 +19,7 @@ export type Result = any; async function simulationTesting(testRunner: TestRunner) { const optionRoundContract = await getOptionRoundContract( testRunner.provider, - testRunner.vaultFacade.vaultContract + testRunner.vaultFacade.vaultContract, ); const simulator = new RoundSimulator(testRunner, optionRoundContract); @@ -27,7 +27,7 @@ async function simulationTesting(testRunner: TestRunner) { const simulationSheets = generateSheet(); const simulationParams = generateSimulationParams( testRunner, - simulationSheets + simulationSheets, ); const data: Results = { results: [] }; @@ -38,11 +38,11 @@ async function simulationTesting(testRunner: TestRunner) { const stringified = JSON.stringify(data); fs.writeFile( `./simulationData/simulationOutput/simulationResults-${Math.floor( - Date.now() / 1000 + Date.now() / 1000, )}.json`, stringified, "utf8", - () => {} + () => {}, ); } @@ -52,23 +52,20 @@ const initial = { liquidityProviders: [1, 2], depositAmounts: ["50000000000000", "50000000000000"], optionBidders: [1, 3], - }; const repeating = { liquidityProviders: [], depositAmounts: [], optionBidders: [1, 3], - }; export const generateSheet = () => { const simulationMarketData: Array = marketData.map((data) => { return { - reservePrice: Math.floor(data.reserve_price), settlementPrice: Math.floor(data.settlement_price), + volatility: data.volatility, + reservePrice: Math.floor(data.reserve_price), strikePrice: Math.floor(data.strike_price), - capLevel: data.cap_level, - volatility:data.volatility, startTime: data.starting_timestamp, endTime: data.ending_timestamp, }; @@ -81,16 +78,16 @@ export const generateSheet = () => { return marketData.reservePrice; }), marketData, - bidAmounts:[Math.random(),Math.random()], - withdrawals:[1,2], - withdrawalAmounts:[Math.random()/2,Math.random()/2], + bidAmounts: [Math.random(), Math.random()], + withdrawals: [1, 2], + withdrawalAmounts: [Math.random() / 2, Math.random() / 2], } as SimulationSheet; } else return { ...repeating, - bidAmounts:[Math.random(),Math.random()], - withdrawals:[1,2], - withdrawalAmounts:[Math.random()/2,Math.random()/2], + bidAmounts: [Math.random(), Math.random()], + withdrawals: [1, 2], + withdrawalAmounts: [Math.random() / 2, Math.random() / 2], bidPrices: initial.optionBidders.map((bidder) => { return marketData.reservePrice; }), diff --git a/scripts/utils/constants.ts b/scripts/utils/constants.ts index 1963110c..60956593 100644 --- a/scripts/utils/constants.ts +++ b/scripts/utils/constants.ts @@ -17,14 +17,14 @@ type VaultConstructorArgs = { roundTransitionPeriod: number; auctionRunTime: number; optionRunTime: number; - kFactor:number; + kFactor: number; }; type ConstructorArgs = { eth: EthConstructorArgs; vault: VaultConstructorArgs; optionRound: string; - marketAggregator: string; + factRegistry: string; }; const nodeUrlMapping: { [key: string]: string } = { production: "", @@ -43,11 +43,10 @@ const constructorArgs: { [key: string]: ConstructorArgs } = { roundTransitionPeriod: 32000, auctionRunTime: 23000, optionRunTime: 23000, - kFactor:10000, + kFactor: 10000, }, optionRound: "", - marketAggregator: "", - + factRegistry: "", }, }; const accountDetailsMapping: { [key: string]: AccountDetailsType } = { diff --git a/scripts/utils/deployment/declareContracts.ts b/scripts/utils/deployment/declareContracts.ts index 940b1778..16e8ad46 100644 --- a/scripts/utils/deployment/declareContracts.ts +++ b/scripts/utils/deployment/declareContracts.ts @@ -1,17 +1,17 @@ import { Account } from "starknet"; -import ethSierra from "../../../target/dev/pitch_lake_starknet_Eth.contract_class.json" assert { type: "json" }; -import ethCasm from "../../../target/dev/pitch_lake_starknet_Eth.compiled_contract_class.json" assert { type: "json" }; -import vaultSierra from "../../../target/dev/pitch_lake_starknet_Vault.contract_class.json" assert { type: "json" }; -import vaultCasm from "../../../target/dev/pitch_lake_starknet_Vault.compiled_contract_class.json" assert { type: "json" }; -import optionRoundSieraa from "../../../target/dev/pitch_lake_starknet_OptionRound.contract_class.json" assert { type: "json" }; -import optionRoundCasm from "../../../target/dev/pitch_lake_starknet_OptionRound.compiled_contract_class.json" assert { type: "json" }; -import marketAggregatorSierra from "../../../target/dev/pitch_lake_starknet_MarketAggregator.contract_class.json" assert { type: "json" }; -import marketAggregatorCasm from "../../../target/dev/pitch_lake_starknet_MarketAggregator.compiled_contract_class.json" assert { type: "json" }; +import ethSierra from "../../../target/dev/pitch_lake_Eth.contract_class.json" assert { type: "json" }; +import ethCasm from "../../../target/dev/pitch_lake_Eth.compiled_contract_class.json" assert { type: "json" }; +import vaultSierra from "../../../target/dev/pitch_lake_Vault.contract_class.json" assert { type: "json" }; +import vaultCasm from "../../../target/dev/pitch_lake_Vault.compiled_contract_class.json" assert { type: "json" }; +import optionRoundSieraa from "../../../target/dev/pitch_lake_OptionRound.contract_class.json" assert { type: "json" }; +import optionRoundCasm from "../../../target/dev/pitch_lake_OptionRound.compiled_contract_class.json" assert { type: "json" }; +import factRegistrySierra from "../../../target/dev/pitch_lake_FactRegistry.contract_class.json" assert { type: "json" }; +import factRegistryCasm from "../../../target/dev/pitch_lake_FactRegistry.compiled_contract_class.json" assert { type: "json" }; async function declareContract( account: Account, sierra: any, casm: any, - placeholder: string + placeholder: string, ) { try { const declareResult = await account.declare({ @@ -34,7 +34,7 @@ async function declareContracts(account: Account) { account, vaultSierra, vaultCasm, - "vault" + "vault", ); if (!vaultHash) { @@ -44,27 +44,27 @@ async function declareContracts(account: Account) { account, optionRoundSieraa, optionRoundCasm, - "optionRound" + "optionRound", ); if (!optionRoundHash) { throw Error("OptionRound Deploy Failed"); } - let marketAggregatorHash = await declareContract( + let factRegistryHash = await declareContract( account, - marketAggregatorSierra, - marketAggregatorCasm, - "marketAggregator" + factRegistrySierra, + factRegistryCasm, + "factRegistry", ); - if (!marketAggregatorHash) { - throw Error("MarketAggregator Deploy Failed"); + if (!factRegistryHash) { + throw Error("FactRegistry Deploy Failed"); } return { ethHash, vaultHash, optionRoundHash, - marketAggregatorHash, + factRegistryHash, }; } export { declareContract, declareContracts }; diff --git a/scripts/utils/deployment/deployContracts.ts b/scripts/utils/deployment/deployContracts.ts index af2a73b9..80df6db8 100644 --- a/scripts/utils/deployment/deployContracts.ts +++ b/scripts/utils/deployment/deployContracts.ts @@ -2,13 +2,13 @@ import { Account } from "starknet"; // deployContracts.js import { CallData, CairoCustomEnum } from "starknet"; -import vaultSierra from "../../../target/dev/pitch_lake_starknet_Vault.contract_class.json" assert { type: "json" }; +import vaultSierra from "../../../target/dev/pitch_lake_Vault.contract_class.json" assert { type: "json" }; import { constructorArgs } from "../constants"; async function deployEthContract( enviornment: string, account: Account, - classHash: string + classHash: string, ) { let constructorArgsEth = [...Object.values(constructorArgs[enviornment].eth)]; const deployResult = await account.deploy({ @@ -21,16 +21,14 @@ async function deployEthContract( return deployResult.contract_address[0]; } - - async function deployVaultContract( enviornment: string, account: Account, contractAddresses: { ethContract: string; - marketAggregatorContract: string; + factRegistryContract: string; }, - hashes: { vault: string; optionRound: string } + hashes: { vault: string; optionRound: string }, ) { const contractCallData = new CallData(vaultSierra.abi); @@ -40,8 +38,8 @@ async function deployVaultContract( auction_run_time: constants.auctionRunTime, option_run_time: constants.optionRunTime, eth_address: contractAddresses.ethContract, - vault_type: new CairoCustomEnum({ InTheMoney: {} }), - market_aggregator_address: contractAddresses.marketAggregatorContract, + vault_type: new CairoCustomEnum({ AtTheMoney: {} }), + fact_registry_address: contractAddresses.factRegistryContract, option_round_class_hash: hashes.optionRound, }); @@ -54,18 +52,18 @@ async function deployVaultContract( return deployResult.contract_address[0]; } -async function deployMarketAggregator( +async function deployFactRegistry( enviornment: string, account: Account, - marketAggregatorClassHash: string + factRegistryClassHash: string, ) { const deployResult = await account.deploy({ - classHash: marketAggregatorClassHash, + classHash: factRegistryClassHash, }); console.log( "Market Aggregator contract is deployed successfully at - ", - deployResult + deployResult, ); return deployResult.contract_address[0]; } @@ -77,49 +75,49 @@ async function deployContracts( ethHash: string; vaultHash: string; optionRoundHash: string; - marketAggregatorHash: string; + factRegistryHash: string; }, ) { let ethAddress = await deployEthContract( enviornment, account, - hashes.ethHash + hashes.ethHash, ); if (!ethAddress) { throw Error("Eth deploy failed"); } - let marketAggregatorAddress = await deployMarketAggregator( + let factRegistryAddress = await deployFactRegistry( enviornment, account, - hashes.marketAggregatorHash + hashes.factRegistryHash, ); - if (!marketAggregatorAddress) { - throw Error("MktAgg deploy failed"); + if (!factRegistryAddress) { + throw Error("FactRegistry deploy failed"); } let vaultAddress = await deployVaultContract( enviornment, account, { - marketAggregatorContract: marketAggregatorAddress, + factRegistryContract: factRegistryAddress, ethContract: ethAddress, }, - { optionRound: hashes.optionRoundHash, vault: hashes.vaultHash } + { optionRound: hashes.optionRoundHash, vault: hashes.vaultHash }, ); if (!vaultAddress) { throw Error("Eth deploy failed"); } return { ethAddress, - marketAggregatorAddress, + factRegistryAddress, vaultAddress, }; } export { deployEthContract, - deployMarketAggregator, + deployFactRegistry, deployVaultContract, deployContracts, }; diff --git a/scripts/utils/facades/RoundSimulator.ts b/scripts/utils/facades/RoundSimulator.ts index 1b136347..e78d7137 100644 --- a/scripts/utils/facades/RoundSimulator.ts +++ b/scripts/utils/facades/RoundSimulator.ts @@ -9,7 +9,7 @@ import { WithdrawArgs, } from "./types"; import { TestRunner } from "./TestRunner"; -import { getOptionRoundContract, getOptionRoundFacade } from "../helpers/setup"; +import { getOptionRoundContract } from "../helpers/setup"; import { optionRoundABI } from "../../abi"; import { OptionRoundFacade } from "./optionRoundFacade"; @@ -60,7 +60,7 @@ export class RoundSimulator { constructor( testRunner: TestRunner, - optionRoundContract: TypedContractV2 + optionRoundContract: TypedContractV2, ) { this.testRunner = testRunner; this.optionRoundFacade = new OptionRoundFacade(optionRoundContract); @@ -71,36 +71,36 @@ export class RoundSimulator { async simulateRound(params: SimulationParameters) { const optionRoundContract = await getOptionRoundContract( this.testRunner.provider, - this.testRunner.vaultFacade.vaultContract + this.testRunner.vaultFacade.vaultContract, ); this.optionRoundFacade = new OptionRoundFacade(optionRoundContract); //Add market agg setter here or somewhere in openState const openStateData: StateData = await this.simulateOpenState( - params.depositAllArgs + params.depositAllArgs, ); const auctioningStateData: StateData = await this.simulateAuctioningState( params.bidAllArgs, - params.marketData ); const optionsAvailable = await this.optionRoundFacade.getTotalOptionsAvailable(); const runningStateData: StateData = await this.simulateRunningState( params.refundAllArgs, - params.withdrawPremiumArgs + params.withdrawPremiumArgs, ); const settledStateData: StateData = await this.simulateSettledState( params.exerciseOptionsAllArgs, - params.withdrawalArgs + params.withdrawalArgs, + params.marketData, ); const optionsSold = await this.optionRoundFacade.optionRoundContract.get_options_sold(); const ethBalanceVault = await this.testRunner.ethFacade.getBalance( - this.testRunner.vaultFacade.vaultContract.address + this.testRunner.vaultFacade.vaultContract.address, ); const ethBalanceRound = await this.testRunner.ethFacade.getBalance( - this.optionRoundFacade.optionRoundContract.address + this.optionRoundFacade.optionRoundContract.address, ); if (params.marketData.startTime && params.marketData.endTime) { //Mock timestamps if present on the marketData @@ -130,7 +130,7 @@ export class RoundSimulator { async captureLockedUnlockedBalances() { const lpLockedBalancesBigInt = await this.testRunner.getLPLockedBalanceAll( - this.lpAccounts + this.lpAccounts, ); const lpUnlockedBalancesBigint = await this.testRunner.getLPUnlockedBalanceAll(this.lpAccounts); @@ -145,7 +145,7 @@ export class RoundSimulator { async captureEthBalancesLiquidityProviders() { const ethBalancesBigInt = await this.testRunner.getBalancesAll( - this.lpAccounts + this.lpAccounts, ); const ethBalances = ethBalancesBigInt.map((balance) => { return balance.toString(); @@ -164,7 +164,7 @@ export class RoundSimulator { async captureEthBalancesOptionBidders() { const ethBalancesBigInt = await this.testRunner.getBalancesAll( - this.bidderAccounts + this.bidderAccounts, ); const ethBalances = ethBalancesBigInt.map((balance) => { return balance.toString(); @@ -183,11 +183,8 @@ export class RoundSimulator { }; //Add market data setter abstraction after Jithin's merge } - async simulateAuctioningState( - bidAllArgs: Array, - marketData: MarketData - ) { - await this.testRunner.startAuctionBystander(marketData); + async simulateAuctioningState(bidAllArgs: Array) { + await this.testRunner.startAuctionBystander(); const lockedUnlockedBalances = await this.captureLockedUnlockedBalances(); const vaultBalances = await this.captureVaultBalances(); @@ -222,7 +219,7 @@ export class RoundSimulator { } async simulateRunningState( refundAllArgs: Array, - withdrawPremiumArgs: Array + withdrawPremiumArgs: Array, ) { await this.testRunner.endAuctionBystander(); @@ -233,7 +230,7 @@ export class RoundSimulator { for (const args of withdrawPremiumArgs) { const lockedBalance = await this.testRunner.vaultFacade.getLPLockedBalance( - args.account.address + args.account.address, ); const premiumsToWithdraw = (BigInt(lockedBalance) * BigInt(totalPremiums)) / @@ -257,29 +254,30 @@ export class RoundSimulator { } async simulateSettledState( exerciseOptionsArgs: Array, - withdrawalArgs: Array + withdrawalArgs: Array, + marketData: MarketData, ) { const data = await this.optionRoundFacade.optionRoundContract.get_state(); - await this.testRunner.settleOptionRoundBystander(); + await this.testRunner.settleOptionRoundBystander(marketData); const withdrawArgsAdjusted: Array = []; for (const args of withdrawalArgs) { const unlockedBalance = await this.testRunner.vaultFacade.getLPUnlockedBalance( - args.account.address + args.account.address, ); - console.log("UNLOCKED",unlockedBalance); - withdrawArgsAdjusted.push({ - account:args.account, - amount:Math.floor(Number(args.amount)*Number(unlockedBalance)) - }) + console.log("UNLOCKED", unlockedBalance); + withdrawArgsAdjusted.push({ + account: args.account, + amount: Math.floor(Number(args.amount) * Number(unlockedBalance)), + }); } const lpBefore = await this.captureLockedUnlockedBalances(); await this.testRunner.withdrawAll(withdrawArgsAdjusted); const lpAfter = await this.captureLockedUnlockedBalances(); - console.log("ARGS:",withdrawalArgs,"\nADjusted:",withdrawArgsAdjusted) + console.log("ARGS:", withdrawalArgs, "\nADjusted:", withdrawArgsAdjusted); const lockedUnlockedBalances = await this.captureLockedUnlockedBalances(); const vaultBalances = await this.captureVaultBalances(); await this.optionRoundFacade.exerciseOptionsAll(exerciseOptionsArgs); diff --git a/scripts/utils/facades/TestRunner.ts b/scripts/utils/facades/TestRunner.ts index 342883d7..68207fbb 100644 --- a/scripts/utils/facades/TestRunner.ts +++ b/scripts/utils/facades/TestRunner.ts @@ -7,11 +7,12 @@ import { DepositArgs, MarketData, WithdrawArgs, + JobRequest, } from "./types"; import { getOptionRoundContract, getOptionRoundFacade } from "../helpers/setup"; import { getNow, timeskipNextBlock } from "../katana"; import { getAccount, getCustomAccount, stringToHex } from "../helpers/common"; -import { MarketAggregatorFacade } from "./marketAggregatorFacade"; +import { FactRegistryFacade } from "./factRegistryFacade"; import { liquidityProviders, optionBidders } from "../constants"; export type ResultSheet = { @@ -40,7 +41,7 @@ export class TestRunner { provider: Provider, vaultAddress: string, ethAddress: string, - constants: Constants + constants: Constants, ) { this.vaultFacade = new VaultFacade(vaultAddress, provider); this.ethFacade = new ERC20Facade(ethAddress, provider); @@ -51,7 +52,7 @@ export class TestRunner { testResults = async ( accounts: Array, params: Array, - method: Methods + method: Methods, ) => { const before: Map< StoragePoints, @@ -99,10 +100,10 @@ export class TestRunner { const balances = await Promise.all( accounts.map(async (account: Account) => { const res = await this.vaultFacade.getLPUnlockedBalance( - account.address + account.address, ); return res; - }) + }), ); return balances; }; @@ -112,7 +113,7 @@ export class TestRunner { accounts.map(async (account: Account) => { const res = await this.vaultFacade.getLPLockedBalance(account.address); return res; - }) + }), ); return balances; }; @@ -134,7 +135,7 @@ export class TestRunner { accounts.map(async (account: Account) => { const balance = await this.ethFacade.getBalance(account.address); return balance; - }) + }), ); return balances; }; @@ -148,7 +149,7 @@ export class TestRunner { accelerateToAuctioning = async () => { const optionRoundContract = await getOptionRoundContract( this.provider, - this.vaultFacade.vaultContract + this.vaultFacade.vaultContract, ); const currentTime = await getNow(this.provider); const auctionStartDate = await optionRoundContract.get_auction_start_date(); @@ -157,18 +158,18 @@ export class TestRunner { "currentTime:", currentTime, "\nauctionStartDate:", - auctionStartDate + auctionStartDate, ); await timeskipNextBlock( Number(auctionStartDate) - Number(currentTime), - this.provider.channel.nodeUrl + this.provider.channel.nodeUrl, ); }; accelerateToRunning = async () => { const optionRoundContract = await getOptionRoundContract( this.provider, - this.vaultFacade.vaultContract + this.vaultFacade.vaultContract, ); const currentTime = await getNow(this.provider); @@ -176,14 +177,14 @@ export class TestRunner { await timeskipNextBlock( Number(auctionEndDate) - Number(currentTime) + 1, - this.provider.channel.nodeUrl + this.provider.channel.nodeUrl, ); }; accelerateToSettled = async () => { const optionRoundContract = await getOptionRoundContract( this.provider, - this.vaultFacade.vaultContract + this.vaultFacade.vaultContract, ); const currentTime = await getNow(this.provider); @@ -192,70 +193,79 @@ export class TestRunner { await timeskipNextBlock( Number(optionSettleDate) - Number(currentTime), - this.provider.channel.nodeUrl + this.provider.channel.nodeUrl, ); }; //@note Only works for katana dev instance with a --dev flag - startAuctionBystander = async (marketData: MarketData) => { - console.log("MARKETDATA:", marketData); - const devAccount = getAccount("dev", this.provider); - //Set market aggregator reserve_price - const marketAggregatorString = - await this.vaultFacade.vaultContract.get_market_aggregator_address(); - const marketAggregatorAddress = "0x" + stringToHex(marketAggregatorString); - const marketAggFacade = new MarketAggregatorFacade( - marketAggregatorAddress, - this.provider - ); - const optionRound = await getOptionRoundFacade( - this.provider, - this.vaultFacade.vaultContract - ); - - const roundId = await optionRound.getRoundId(); - - const settleDate = - await optionRound.optionRoundContract.get_option_settlement_date(); - - const startDate = await optionRound.optionRoundContract.get_auction_start_date(); - const twapPeriod = BigInt(60 * 60 * 24 * 14); - - const auctionRunTime = - await this.vaultFacade.vaultContract.get_auction_run_time(); - const optionRunTime = - await this.vaultFacade.vaultContract.get_option_run_time(); - const roundTransitionPeriod = - await this.vaultFacade.vaultContract.get_round_transition_period(); - - const duration = - BigInt(auctionRunTime) + - BigInt(optionRunTime) + - BigInt(roundTransitionPeriod); - const endDatePeriodA = BigInt(startDate) - const startDatePeriodA = endDatePeriodA-twapPeriod - - const endDatePeriodB = endDatePeriodA-BigInt(roundTransitionPeriod); - const startDatePeriodB = endDatePeriodB-twapPeriod; - - console.log("START DATE, SETTLE DATE", startDate, "\n", settleDate); - await marketAggFacade.setMarketParameters({ - devAccount, - vaultAddress: this.vaultFacade.vaultContract.address, - roundId: roundId, - startDatePeriodA, - startDatePeriodB, - endDatePeriodA, - endDatePeriodB, - marketData, - }); - - await this.vaultFacade.vaultContract.update_round_params(); - + startAuctionBystander = async () => { await this.accelerateToAuctioning(); + const devAccount = getAccount("dev", this.provider); this.vaultFacade.vaultContract.connect(devAccount); await this.vaultFacade.vaultContract.start_auction(); + + // const settleDate = + // await optionRound.optionRoundContract.get_option_settlement_date(); + // + // const startDate = + // await optionRound.optionRoundContract.get_auction_start_date(); + // const twapPeriod = BigInt(60 * 60 * 24 * 14); + // + // const auctionRunTime = + // await this.vaultFacade.vaultContract.get_auction_run_time(); + // const optionRunTime = + // await this.vaultFacade.vaultContract.get_option_run_time(); + // const roundTransitionPeriod = + // await this.vaultFacade.vaultContract.get_round_transition_period(); + // + // const duration = + // BigInt(auctionRunTime) + + // BigInt(optionRunTime) + + // BigInt(roundTransitionPeriod); + // const endDatePeriodA = BigInt(startDate); + // const startDatePeriodA = endDatePeriodA - twapPeriod; + // + // const endDatePeriodB = endDatePeriodA - BigInt(roundTransitionPeriod); + // const startDatePeriodB = endDatePeriodB - twapPeriod; + // + // console.log("START DATE, SETTLE DATE", startDate, "\n", settleDate); + + // const optionSettlementDate = parseInt( + // ( + // await optionRound.optionRoundContract.get_option_settlement_date() + // ).toString(), + // ); + // + // const twapRange = 3600 * 24 * 30; + // const volatilityRange = 3600 * 24 * 90; + // const reservePriceRange = 3600 * 24 * 90; + // + // const job_request: JobRequest = { + // identifiers: ["PITCH_LAKE_V1"], + // params: { + // twap: [ + // parseInt(optionSettlementDate.toString()) - twapRange, + // optionSettlementDate, + // ], + // volatility: [ + // optionSettlementDate - volatilityRange, + // optionSettlementDate, + // ], + // reserve_price: [ + // optionSettlementDate - reservePriceRange, + // optionSettlementDate, + // ], + // }, + // }; + // + // await factRegFacade.setMarketParameters({ + // devAccount, + // job_request, + // market_data: marketData, + // }); + // + // await this.vaultFacade.vaultContract.update_round_params(); }; endAuctionBystander = async () => { @@ -264,10 +274,62 @@ export class TestRunner { await this.vaultFacade.endAuction(devAccount); }; - settleOptionRoundBystander = async () => { - await this.accelerateToSettled(); + //@note Only works for katana dev instance with a --dev flag + settleOptionRoundBystander = async (marketData: MarketData) => { + console.log("MARKETDATA:", marketData); + + const factRegistryString = + await this.vaultFacade.vaultContract.get_market_aggregator_address(); + const factRegistryAddress = "0x" + stringToHex(factRegistryString); + const factRegFacade = new FactRegistryFacade( + factRegistryAddress, + this.provider, + ); + const optionRound = await getOptionRoundFacade( + this.provider, + this.vaultFacade.vaultContract, + ); + + // Mock JobRequest + + const jobRequest: JobRequest = await optionRound.createJobRequest(); const devAccount = getAccount("dev", this.provider); - await this.vaultFacade.settleOptionRound(devAccount); + await factRegFacade.setMarketParameters({ + devAccount, + jobRequest, + marketData, + }); + + await this.accelerateToSettled(); + await this.vaultFacade.settleOptionRound(devAccount, jobRequest); + + // const optionSettlementDate = parseInt( + // ( + // await optionRound.optionRoundContract.get_option_settlement_date() + // ).toString(), + // ); + + // const twapRange = 3600 * 24 * 30; + // const volatilityRange = 3600 * 24 * 90; + // const reservePriceRange = 3600 * 24 * 90; + + // const job_request: JobRequest = { + // identifiers: ["PITCH_LAKE_V1"], + // params: { + // twap: [ + // parseInt(optionSettlementDate.toString()) - twapRange, + // optionSettlementDate, + // ], + // volatility: [ + // optionSettlementDate - volatilityRange, + // optionSettlementDate, + // ], + // reserve_price: [ + // optionSettlementDate - reservePriceRange, + // optionSettlementDate, + // ], + // }, + // }; }; getLiquidityProviderAccounts = (length: number) => { @@ -277,8 +339,8 @@ export class TestRunner { getCustomAccount( this.provider, liquidityProviders[i].account, - liquidityProviders[i].privateKey - ) + liquidityProviders[i].privateKey, + ), ); } return liquidityProviderAccounts; @@ -291,8 +353,8 @@ export class TestRunner { getCustomAccount( this.provider, optionBidders[i].account, - optionBidders[i].privateKey - ) + optionBidders[i].privateKey, + ), ); } return optionBidderAccounts; diff --git a/scripts/utils/facades/factRegistryFacade.ts b/scripts/utils/facades/factRegistryFacade.ts new file mode 100644 index 00000000..1f2e36ef --- /dev/null +++ b/scripts/utils/facades/factRegistryFacade.ts @@ -0,0 +1,128 @@ +import { Account, Contract, Provider, TypedContractV2 } from "starknet"; +import { factRegistryABI } from "../../abi"; +import { MarketData, JobRequest } from "./types"; + +export class FactRegistryFacade { + factRegistryContract: TypedContractV2; + + constructor(factRegistryAddress: string, provider: Provider) { + this.factRegistryContract = new Contract( + factRegistryABI, + factRegistryAddress, + provider, + ).typedv2(factRegistryABI); + } + + async setFact( + account: Account, + job_request: JobRequest, + market_data: MarketData, + ) { + this.factRegistryContract.connect(account); + + await this.factRegistryContract.set_fact(job_request, [ + market_data.settlementPrice, + market_data.volatility, + market_data.reservePrice, + ]); + } + + // async setVolatility( + // account: Account, + // vaultAddress: string, + // roundId: number | bigint, + // volatility: number | bigint, + // ) { + // this.marketAggregatorContract.connect(account); + // await this.marketAggregatorContract.set_volatility_for_round( + // vaultAddress, + // roundId, + // volatility, + // ); + // } + // async setCapLevel( + // account: Account, + // vaultAddress: string, + // roundId: number | bigint, + // capLevel: number | bigint, + // ) { + // this.marketAggregatorContract.connect(account); + // await this.marketAggregatorContract.set_cap_level_for_round( + // vaultAddress, + // roundId, + // capLevel, + // ); + // } + // async setReservePrice( + // account: Account, + // vaultAddress: string, + // roundId: bigint | number, + // reservePrice: number | bigint, + // ) { + // this.marketAggregatorContract.connect(account); + // await this.marketAggregatorContract.set_reserve_price_for_round( + // vaultAddress, + // roundId, + // reservePrice, + // ); + // } + // async setTWAP( + // account: Account, + // from: number | bigint, + // to: number | bigint, + // reservePrice: number | bigint, + // ) { + // this.marketAggregatorContract.connect(account); + // await this.marketAggregatorContract.set_TWAP_for_time_period( + // from, + // to, + // reservePrice, + // ); + // } + + async setMarketParameters({ + devAccount, + jobRequest, + marketData, + }: { + devAccount: Account; + jobRequest: JobRequest; + marketData: MarketData; + }) { + // job_request and data + await this.setFact(devAccount, jobRequest, marketData); + + // await this.setReservePrice( + // devAccount, + // vaultAddress, + // roundId, + // marketData.reservePrice, + // ); + // await this.setCapLevel( + // devAccount, + // vaultAddress, + // roundId, + // marketData.capLevel, + // ); + // + // await this.setVolatility( + // devAccount, + // vaultAddress, + // roundId, + // marketData.capLevel, + // ); + // + // await this.setTWAP( + // devAccount, + // startDatePeriodA, + // endDatePeriodA, + // marketData.settlementPrice, + // ); + // await this.setTWAP( + // devAccount, + // startDatePeriodB, + // endDatePeriodB, + // marketData.settlementPrice, + // ); + } +} diff --git a/scripts/utils/facades/marketAggregatorFacade.ts b/scripts/utils/facades/marketAggregatorFacade.ts deleted file mode 100644 index f3241471..00000000 --- a/scripts/utils/facades/marketAggregatorFacade.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { Account, Contract, Provider, TypedContractV2 } from "starknet"; -import { marketAggregatorABI } from "../../abi"; -import { MarketData } from "./types"; - -export class MarketAggregatorFacade { - marketAggregatorContract: TypedContractV2; - - constructor(marketAggregatorAddress: string, provider: Provider) { - this.marketAggregatorContract = new Contract( - marketAggregatorABI, - marketAggregatorAddress, - provider - ).typedv2(marketAggregatorABI); - } - - async setVolatility( - account: Account, - vaultAddress: string, - roundId: number | bigint, - volatility: number | bigint - ) { - this.marketAggregatorContract.connect(account); - await this.marketAggregatorContract.set_volatility_for_round( - vaultAddress, - roundId, - volatility - ); - } - async setCapLevel( - account: Account, - vaultAddress: string, - roundId: number | bigint, - capLevel: number | bigint - ) { - this.marketAggregatorContract.connect(account); - await this.marketAggregatorContract.set_cap_level_for_round( - vaultAddress, - roundId, - capLevel - ); - } - async setReservePrice( - account: Account, - vaultAddress: string, - roundId: bigint | number, - reservePrice: number | bigint - ) { - this.marketAggregatorContract.connect(account); - await this.marketAggregatorContract.set_reserve_price_for_round( - vaultAddress, - roundId, - reservePrice - ); - } - async setTWAP( - account: Account, - from: number | bigint, - to: number | bigint, - reservePrice: number | bigint - ) { - this.marketAggregatorContract.connect(account); - await this.marketAggregatorContract.set_TWAP_for_time_period( - from, - to, - reservePrice - ); - } - - async setMarketParameters({ - devAccount, - vaultAddress, - roundId, - startDatePeriodA, - startDatePeriodB, - endDatePeriodA, - endDatePeriodB, - marketData, - }: { - devAccount: Account; - vaultAddress: string; - roundId: number | bigint; - startDatePeriodA: number | bigint; - startDatePeriodB:number|bigint; - endDatePeriodA: number|bigint; - endDatePeriodB: number | bigint; - marketData: MarketData; - }) { - await this.setReservePrice( - devAccount, - vaultAddress, - roundId, - marketData.reservePrice - ); - await this.setCapLevel( - devAccount, - vaultAddress, - roundId, - marketData.capLevel - ); - - await this.setVolatility( - devAccount, - vaultAddress, - roundId, - marketData.capLevel - ) - - await this.setTWAP( - devAccount, - startDatePeriodA, - endDatePeriodA, - marketData.settlementPrice - ); - await this.setTWAP( - devAccount, - startDatePeriodB, - endDatePeriodB, - marketData.settlementPrice - ); - } -} diff --git a/scripts/utils/facades/optionRoundFacade.ts b/scripts/utils/facades/optionRoundFacade.ts index 77ed6960..943c3747 100644 --- a/scripts/utils/facades/optionRoundFacade.ts +++ b/scripts/utils/facades/optionRoundFacade.ts @@ -9,9 +9,12 @@ import { TokenizableOptionsArgs, TokenizeOptionArgs, UpdateBidArgs, + JobRequest, + MarketData, } from "./types"; import { optionRoundABI } from "../../abi"; import { convertToBigInt } from "../helpers/common"; +import { CairoCustomEnum } from "starknet"; export class OptionRoundFacade { optionRoundContract: TypedContractV2; @@ -20,9 +23,31 @@ export class OptionRoundFacade { this.optionRoundContract = optionRoundContract; } + async createJobRequest() { + const state = await this.optionRoundContract.get_state(); + const upperBound: number = + state && Object.keys(state)[0] === "Open" + ? Number(await this.optionRoundContract.get_auction_start_date()) - 1 + : Number(await this.optionRoundContract.get_option_settlement_date()) - + 1; - async getStartingLiquidity(){ + const DAY = 24 * 3600; + const job_request: JobRequest = { + identifiers: ["PITCH_LAKE_V1"], + params: { + twap: [upperBound - 30 * DAY, upperBound], + volatility: [upperBound - 90 * DAY, upperBound], + reserve_price: [upperBound - 90 * DAY, upperBound], + }, + }; + + return job_request; + + // + } + + async getStartingLiquidity() { const res = await this.optionRoundContract.get_starting_liquidity(); return convertToBigInt(res); } @@ -83,7 +108,7 @@ export class OptionRoundFacade { accounts.map(async (account: Account) => { const bidData = await this.getBidsFor(account.address); return bidData; - }) + }), ); return bids; } @@ -102,7 +127,7 @@ export class OptionRoundFacade { const data = await this.optionRoundContract.place_bid(amount, price); } catch (err) { const error = err as LibraryError; - console.log(error.name,from,amount,price,error.message,error.cause); + console.log(error.name, from, amount, price, error.message, error.cause); } } @@ -114,9 +139,8 @@ export class OptionRoundFacade { async getRefundableBidsFor({ optionBuyer }: RefundableBidsArgs) { try { - const res = await this.optionRoundContract.get_refundable_bids_for( - optionBuyer - ); + const res = + await this.optionRoundContract.get_refundable_bids_for(optionBuyer); return convertToBigInt(res); } catch (err) { console.log(err); @@ -129,7 +153,7 @@ export class OptionRoundFacade { optionBuyers.map(async (account: Account) => { const balance = await this.getTotalOptionsBalanceFor(account.address); return balance; - }) + }), ); return optionsBalances; } catch (err) { @@ -139,9 +163,8 @@ export class OptionRoundFacade { async getTotalOptionsBalanceFor(optionBuyer: string) { try { - const res = await this.optionRoundContract.get_account_total_options( - optionBuyer - ); + const res = + await this.optionRoundContract.get_account_total_options(optionBuyer); return convertToBigInt(res); } catch (err) { console.log(err); @@ -150,9 +173,8 @@ export class OptionRoundFacade { async getPayoutBalanceFor({ optionBuyer }: PayoutBalanceArgs) { try { - const res = await this.optionRoundContract.get_payout_balance_for( - optionBuyer - ); + const res = + await this.optionRoundContract.get_payout_balance_for(optionBuyer); return convertToBigInt(res); } catch (err) { console.log(err); @@ -161,9 +183,8 @@ export class OptionRoundFacade { async getTokenizableOptionsFor({ optionBuyer }: TokenizableOptionsArgs) { try { - const res = await this.optionRoundContract.get_tokenizable_options_for( - optionBuyer - ); + const res = + await this.optionRoundContract.get_tokenizable_options_for(optionBuyer); return convertToBigInt(res); } catch (err) { console.log(err); @@ -172,9 +193,8 @@ export class OptionRoundFacade { async refundUnusedBids({ from, optionBidder }: RefundUnusedBidsArgs) { this.optionRoundContract.connect(from); - const data = await this.optionRoundContract.refund_unused_bids( - optionBidder - ); + const data = + await this.optionRoundContract.refund_unused_bids(optionBidder); console.log("refund unused bids inside -> ", data); // @note: here it will return the total refundable_balance @@ -185,7 +205,7 @@ export class OptionRoundFacade { } async refundUnusedBidsAll( - refundUnusedBidsAllArgs: Array + refundUnusedBidsAllArgs: Array, ) { for (const refundArgs of refundUnusedBidsAllArgs) { await this.refundUnusedBids(refundArgs); @@ -206,7 +226,6 @@ export class OptionRoundFacade { for (const exerciseOptionsArgs of exerciseOptionData) { await this.exerciseOptions(exerciseOptionsArgs); } - } async tokenizeOptions({ from }: TokenizeOptionArgs) { diff --git a/scripts/utils/facades/types.ts b/scripts/utils/facades/types.ts index 94bab3a7..ba19b5ea 100644 --- a/scripts/utils/facades/types.ts +++ b/scripts/utils/facades/types.ts @@ -1,5 +1,16 @@ import { Account } from "starknet"; +// Fossil + +export type JobRequest = { + identifiers: string[]; + params: { + twap: [number, number]; + volatility: [number, number]; + reserve_price: [number, number]; + }; +}; + //EthTypes export type ApprovalArgs = { owner: Account; @@ -78,15 +89,16 @@ export type Constants = { settlementPrice: number | bigint; strikePrice: number | bigint; capLevel: number | bigint; - volatility: number|bigint; + volatility: number | bigint; }; //Simulation Types export type MarketData = { - reservePrice: number | bigint; settlementPrice: number | bigint; - strikePrice: number | bigint; - capLevel: number | bigint; - startTime?:number|bigint; - endTime?:number|bigint; + volatility: number | bigint; + reservePrice: number | bigint; + strikePrice?: number | bigint; + capLevel?: number | bigint; + startTime?: number | bigint; + endTime?: number | bigint; }; diff --git a/scripts/utils/facades/vaultFacade.ts b/scripts/utils/facades/vaultFacade.ts index d5963e43..d337e4d9 100644 --- a/scripts/utils/facades/vaultFacade.ts +++ b/scripts/utils/facades/vaultFacade.ts @@ -1,13 +1,7 @@ -import { - Account, - Contract, - Provider, - TypedContractV2, -} from "starknet"; +import { Account, Contract, Provider, TypedContractV2 } from "starknet"; import { optionRoundABI, vaultABI } from "../../abi"; -import { DepositArgs, MarketData, WithdrawArgs } from "./types"; +import { JobRequest, DepositArgs, MarketData, WithdrawArgs } from "./types"; import { convertToBigInt, getAccount, stringToHex } from "../helpers/common"; -import { MarketAggregatorFacade } from "./marketAggregatorFacade"; import { getOptionRoundContract } from "../helpers/setup"; export class VaultFacade { @@ -17,16 +11,16 @@ export class VaultFacade { constructor( vaultAddress: string, provider: Provider, - optionRoundAddress?: string + optionRoundAddress?: string, ) { this.vaultContract = new Contract(vaultABI, vaultAddress, provider).typedv2( - vaultABI + vaultABI, ); if (optionRoundAddress) this.currentOptionRound = new Contract( optionRoundABI, optionRoundAddress, - provider + provider, ).typedv2(optionRoundABI); } @@ -58,10 +52,7 @@ export class VaultFacade { async deposit({ from, beneficiary, amount }: DepositArgs) { this.vaultContract.connect(from); try { - const data = await this.vaultContract.deposit( - amount, - beneficiary - ); + const data = await this.vaultContract.deposit(amount, beneficiary); data; } catch (err) { console.log(err); @@ -76,11 +67,11 @@ export class VaultFacade { async endAuction(account: Account) { this.vaultContract.connect(account); - const res = await this.vaultContract.end_auction(); + await this.vaultContract.end_auction(); } - async settleOptionRound(account: Account) { + async settleOptionRound(account: Account, job_request: JobRequest) { this.vaultContract.connect(account); - await this.vaultContract.settle_round(); + await this.vaultContract.settle_round(job_request); } } diff --git a/src/contracts/lp_token.cairo b/src/contracts/lp_token.cairo deleted file mode 100644 index 5bfa4ebc..00000000 --- a/src/contracts/lp_token.cairo +++ /dev/null @@ -1,81 +0,0 @@ -use starknet::{ContractAddress, StorePacking}; -use array::{Array}; -use traits::{Into, TryInto}; -use openzeppelin::token::erc20::interface::ERC20ABIDispatcher; -use openzeppelin::utils::serde::SerializedAppend; - -use pitch_lake_starknet::{ - market_aggregator::interface::{ - IMarketAggregator, IMarketAggregatorDispatcher, IMarketAggregatorDispatcherTrait - }, - types::{OptionRoundState} -}; - -// @note Events for tokeninzing/positionizing in this contract or vault? -#[event] -#[derive(Drop, starknet::Event)] -enum Event {} - -#[starknet::interface] -trait ILPToken { - /// Reads /// - - // The address of th vault contract associated with this contract - fn vault_address(self: @TContractState) -> ContractAddress; - - // The address of the option round associated with this lp token - fn option_round_address(self: @TContractState) -> ContractAddress; -/// Writes /// - -// Burn round tokens and convert them into a position in the vault -//fn convert_to_position(ref self: TContractState, amount: u256); -} -#[starknet::contract] -mod LpToken { - use starknet::{ContractAddress}; - use openzeppelin::token::erc20::ERC20Component; - use pitch_lake_starknet::contracts::{lp_token::{ILPToken}}; - - // ERC20 Component - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - // Exposes snake_case & CamelCase entry points - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - // Allows the contract access to internal functions - impl InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - vault_address: ContractAddress, - option_round_address: ContractAddress, - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event - } - #[constructor] - fn constructor(ref self: ContractState, name: ByteArray, symbol: ByteArray) { - self.erc20.initializer(name, symbol); - } - - #[abi(embed_v0)] - impl LPTokenImpl of ILPToken { - /// Reads /// - - fn vault_address(self: @ContractState) -> ContractAddress { - self.vault_address.read() - } - - fn option_round_address(self: @ContractState) -> ContractAddress { - self.option_round_address.read() - } - /// Writes /// - - //fn convert_to_position(ref self: ContractState, amount: u256) {} - } -} diff --git a/src/contracts/pitch_lake.cairo b/src/contracts/pitch_lake.cairo deleted file mode 100644 index 2f402ad3..00000000 --- a/src/contracts/pitch_lake.cairo +++ /dev/null @@ -1,52 +0,0 @@ -use pitch_lake_starknet::vault::interface::{IVaultDispatcher}; - -#[starknet::interface] -trait IPitchLake { - fn in_the_money_vault(self: @TContractState) -> IVaultDispatcher; - fn out_the_money_vault(self: @TContractState) -> IVaultDispatcher; - fn at_the_money_vault(self: @TContractState) -> IVaultDispatcher; -} - -#[starknet::contract] -mod PitchLake { - use starknet::{ContractAddress, contract_address::ContractAddressZeroable}; - use pitch_lake_starknet::{ - vault::{interface::{IVault, IVaultDispatcher}}, - market_aggregator::interface::{IMarketAggregator, IMarketAggregatorDispatcher} - }; - - #[storage] - struct Storage { - in_the_money_vault: ContractAddress, - out_the_money_vault: ContractAddress, - at_the_money_vault: ContractAddress, - market_aggregator: ContractAddress, - } - - #[constructor] - fn constructor( - ref self: ContractState, - in_the_money_vault_: ContractAddress, - out_the_money_vault_: ContractAddress, - at_the_money_vault_: ContractAddress, - market_aggregator_: ContractAddress - ) { // self.option_round_class_hash.write( option_round_class_hash_); - self.in_the_money_vault.write(in_the_money_vault_); - self.out_the_money_vault.write(out_the_money_vault_); - self.at_the_money_vault.write(at_the_money_vault_); - self.in_the_money_vault.write(market_aggregator_); - } - - #[abi(embed_v0)] - impl PitchLakeImpl of super::IPitchLake { - fn in_the_money_vault(self: @ContractState) -> IVaultDispatcher { - IVaultDispatcher { contract_address: self.in_the_money_vault.read() } - } - fn out_the_money_vault(self: @ContractState) -> IVaultDispatcher { - IVaultDispatcher { contract_address: self.out_the_money_vault.read() } - } - fn at_the_money_vault(self: @ContractState) -> IVaultDispatcher { - IVaultDispatcher { contract_address: self.at_the_money_vault.read() } - } - } -} diff --git a/src/lib.cairo b/src/lib.cairo index b2836e58..cacf89ff 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -4,6 +4,8 @@ mod library { mod eth; mod red_black_tree; mod utils; + mod pricing_utils; + mod constants; } @@ -17,19 +19,5 @@ mod option_round { mod contract; } -mod market_aggregator { - mod interface; - mod contract; - mod types; -} - - -// @note Refactor these into their own modules -mod contracts { - mod pitch_lake; - mod lp_token; -} - #[cfg(test)] mod tests; - diff --git a/src/library/constants.cairo b/src/library/constants.cairo new file mode 100644 index 00000000..7331e8fe --- /dev/null +++ b/src/library/constants.cairo @@ -0,0 +1,6 @@ +const MINUTE: u64 = 60; +const HOUR: u64 = 60 * MINUTE; +const DAY: u64 = 24 * HOUR; +const ROUND_TRANSITION_PERIOD: u64 = 1 * HOUR; +const AUCTION_RUN_TIME: u64 = 6 * HOUR; +const OPTION_RUN_TIME: u64 = 30 * DAY; diff --git a/src/library/eth.cairo b/src/library/eth.cairo index 7b0eabe3..2706f393 100644 --- a/src/library/eth.cairo +++ b/src/library/eth.cairo @@ -7,29 +7,32 @@ #[starknet::contract] mod Eth { - use openzeppelin::token::erc20::ERC20Component; use starknet::ContractAddress; + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; component!(path: ERC20Component, storage: erc20, event: ERC20Event); - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage - } // Exposes snake_case & CamelCase entry points #[abi(embed_v0)] impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; // Allows the contract access to internal functions impl InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + pub erc20: ERC20Component::Storage + } + #[event] #[derive(Drop, starknet::Event)] enum Event { #[flat] ERC20Event: ERC20Component::Event } + #[constructor] fn constructor(ref self: ContractState, initial_supply: u256, recipient: ContractAddress) { self.erc20.initializer("Ethereum", "WETH"); - self.erc20._mint(recipient, initial_supply); + self.erc20.mint(recipient, initial_supply); } } diff --git a/src/library/pricing_utils.cairo b/src/library/pricing_utils.cairo new file mode 100644 index 00000000..6ae2aad5 --- /dev/null +++ b/src/library/pricing_utils.cairo @@ -0,0 +1,58 @@ +use pitch_lake::vault::interface::VaultType; +use pitch_lake::library::utils::min; +use pitch_lake::types::Consts::BPS; + +// Calculate the maximum payout for a single option +fn max_payout_per_option(strike_price: u256, cap_level: u128) -> u256 { + (strike_price * cap_level.into()) / BPS +} + +// Calculate the actual payout for a single option +fn calculate_payout_per_option( + strike_price: u256, cap_level: u128, settlement_price: u256 +) -> u256 { + if (settlement_price <= strike_price) { + 0 + } else { + let uncapped = settlement_price - strike_price; + let capped = max_payout_per_option(strike_price, cap_level); + + min(capped, uncapped) + } +} + +// Calculate the total number of options available to sell in an auction +fn calculate_total_options_available( + starting_liquidity: u256, strike_price: u256, cap_level: u128 +) -> u256 { + let capped = max_payout_per_option(strike_price, cap_level); + match capped == 0 { + // @dev If the max payout per option is 0, then there are 0 options to sell + true => 0, + // @dev Else the number of options available is the starting liquidity divided by + // the capped amount + false => starting_liquidity / capped + } +} + +// @note TODO +fn calculate_cap_level(alpha: u128, volatility: u128) -> u128 { + volatility +} + +// Calculate a round's strike price +// @note TODO +fn calculate_strike_price(vault_type: VaultType, twap: u256, volatility: u128) -> u256 { + let adjustment = (twap * volatility.into()) / BPS; + match vault_type { + VaultType::AtTheMoney(_) => twap, + VaultType::OutOfMoney(_) => twap + adjustment, + VaultType::InTheMoney(_) => { + if adjustment >= twap { + twap / 2 + } else { + twap - adjustment + } + }, + } +} diff --git a/src/library/red_black_tree.cairo b/src/library/red_black_tree.cairo index 66f4944e..ef21b3ca 100644 --- a/src/library/red_black_tree.cairo +++ b/src/library/red_black_tree.cairo @@ -1,10 +1,12 @@ -use pitch_lake_starknet::{library::red_black_tree, types::{Bid}}; +use pitch_lake::{library::red_black_tree, types::{Bid}}; use starknet::ContractAddress; #[starknet::component] pub mod RBTreeComponent { use super::{Bid, ContractAddress}; - use core::{array::ArrayTrait, option::OptionTrait, traits::{IndexView, TryInto}}; + use core::{array::ArrayTrait, option::OptionTrait, traits::{TryInto}}; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess,}; + use starknet::storage::{Map, StoragePathEntry}; const BLACK: bool = false; const RED: bool = true; @@ -12,7 +14,7 @@ pub mod RBTreeComponent { #[storage] struct Storage { root: felt252, - tree: LegacyMap::, + tree: Map::, tree_nonce: u64, clearing_bid_amount_sold: u256, clearing_price: u256, @@ -50,7 +52,7 @@ pub mod RBTreeComponent { if self.root.read().is_zero() { let root_node = self.create_root_node(@value); - self.tree.write(new_node_id, root_node); + self.tree.entry(new_node_id).write(root_node); self.root.write(new_node_id); } else { self.insert_node_recursively(self.root.read(), new_node_id, value); @@ -61,7 +63,7 @@ pub mod RBTreeComponent { } fn _find(self: @ComponentState, bid_id: felt252) -> Bid { - let node: Node = self.tree.read(bid_id); + let node: Node = self.tree.entry(bid_id).read(); return node.value; } @@ -70,7 +72,7 @@ pub mod RBTreeComponent { let mut node: Node = self.tree.read(bid_id); node.value = bid; // let new_node = Node { value: bid, ..node }; - self.tree.write(bid_id, node); + self.tree.entry(bid_id).write(node); } } @@ -88,13 +90,13 @@ pub mod RBTreeComponent { fn find_clearing_price(ref self: ComponentState) -> (u256, u256) { let total_options_available = self._get_total_options_available(); let root: felt252 = self.root.read(); - let root_node: Node = self.tree.read(root); + let root_node: Node = self.tree.entry(root).read(); let root_bid: Bid = root_node.value; let (clearing_felt, remaining_options) = self .traverse_postorder_clearing_price_from_node( root, total_options_available, root_bid.price, root ); - let clearing_node: Node = self.tree.read(clearing_felt); + let clearing_node: Node = self.tree.entry(clearing_felt).read(); let total_options_sold = total_options_available - remaining_options; self.total_options_sold.write(total_options_sold); if (remaining_options == 0) { @@ -122,7 +124,7 @@ pub mod RBTreeComponent { if (current_id == 0) { return (clearing_felt, total_options_available); } - let current_node: Node = self.tree.read(current_id); + let current_node: Node = self.tree.entry(current_id).read(); //Recursive on Right Node let (clearing_felt, mut remaining_options) = self @@ -165,7 +167,7 @@ pub mod RBTreeComponent { } fn is_left_child(ref self: ComponentState, node_id: felt252) -> bool { - let node: Node = self.tree.read(node_id); + let node: Node = self.tree.entry(node_id).read(); let parent_id = node.parent; let parent: Node = self.tree.read(parent_id); return parent.left == node_id; @@ -174,38 +176,38 @@ pub mod RBTreeComponent { fn update_left( ref self: ComponentState, node_id: felt252, left_id: felt252 ) { - let mut node: Node = self.tree.read(node_id); + let mut node: Node = self.tree.entry(node_id).read(); node.left = left_id; - self.tree.write(node_id, node); + self.tree.entry(node_id).write(node); } fn update_right( ref self: ComponentState, node_id: felt252, right_id: felt252 ) { - let mut node: Node = self.tree.read(node_id); + let mut node: Node = self.tree.entry(node_id).read(); node.right = right_id; - self.tree.write(node_id, node); + self.tree.entry(node_id).write(node); } fn update_parent( ref self: ComponentState, node_id: felt252, parent_id: felt252 ) { - let mut node: Node = self.tree.read(node_id); + let mut node: Node = self.tree.entry(node_id).read(); node.parent = parent_id; - self.tree.write(node_id, node); + self.tree.entry(node_id).write(node); } fn get_parent(ref self: ComponentState, node_id: felt252) -> felt252 { if node_id == 0 { 0 } else { - let node: Node = self.tree.read(node_id); + let node: Node = self.tree.entry(node_id).read(); node.parent } } fn is_black(self: @ComponentState, node_id: felt252) -> bool { - let node: Node = self.tree.read(node_id); + let node: Node = self.tree.entry(node_id).read(); node_id == 0 || node.color == BLACK } @@ -213,7 +215,7 @@ pub mod RBTreeComponent { if node_id == 0 { return false; } - let node: Node = self.tree.read(node_id); + let node: Node = self.tree.entry(node_id).read(); node.color == RED } @@ -226,7 +228,7 @@ pub mod RBTreeComponent { if node_id == 0 { return; // Can't set color of null node } - let mut node: Node = self.tree.read(node_id); + let mut node: Node = self.tree.entry(node_id).read(); node.color = color; self.tree.write(node_id, node); } @@ -237,14 +239,14 @@ pub mod RBTreeComponent { ) -> felt252 { let new_node = Node { value: bid, left: 0, right: 0, parent: parent, color: color, }; let bid_id = bid.bid_id; - let parent_node = self.tree.read(parent); + let parent_node = self.tree.entry(parent).read(); if bid <= parent_node.value { self.update_left(parent, bid_id); } else { self.update_right(parent, bid_id); } self.update_parent(bid_id, parent); - self.tree.write(bid_id, new_node); + self.tree.entry(bid_id).write(new_node); return bid_id; } } @@ -280,29 +282,28 @@ pub mod RBTreeComponent { ); queue.append((root_id, 0)); - while !queue - .is_empty() { - let (node_id, level) = queue.pop_front().unwrap(); - let node = self.tree.read(node_id); + while !queue.is_empty() { + let (node_id, level) = queue.pop_front().unwrap(); + let node = self.tree.entry(node_id).read(); - if level > current_level { - current_level = level; - filled_position_in_levels.append(filled_position_in_level); - filled_position_in_level = ArrayTrait::new(); - } + if level > current_level { + current_level = level; + filled_position_in_levels.append(filled_position_in_level); + filled_position_in_level = ArrayTrait::new(); + } - let position = node_positions.get(node_id); + let position = node_positions.get(node_id); - filled_position_in_level.append((node_id, position)); + filled_position_in_level.append((node_id, position)); - if node.left != 0 { - queue.append((node.left, current_level + 1)); - } + if node.left != 0 { + queue.append((node.left, current_level + 1)); + } - if node.right != 0 { - queue.append((node.right, current_level + 1)); - } - }; + if node.right != 0 { + queue.append((node.right, current_level + 1)); + } + }; filled_position_in_levels.append(filled_position_in_level); return filled_position_in_levels; } @@ -317,22 +318,19 @@ pub mod RBTreeComponent { let mut filled_position_in_levels: Array> = ArrayTrait::new(); let mut filled_position_in_level: Array<(u256, bool, u128)> = ArrayTrait::new(); let mut i = 0; - while i < filled_position_in_levels_original - .len() { - let level = filled_position_in_levels_original.at(i.try_into().unwrap()); - let mut j = 0; - while j < level - .len() { - let (node_id, position) = level.at(j.try_into().unwrap()); - let node = self.tree.read(*node_id); - filled_position_in_level - .append((node.value.price, node.color, *position)); - j += 1; - }; - filled_position_in_levels.append(filled_position_in_level); - filled_position_in_level = ArrayTrait::new(); - i += 1; + while i < filled_position_in_levels_original.len() { + let level = filled_position_in_levels_original.at(i.try_into().unwrap()); + let mut j = 0; + while j < level.len() { + let (node_id, position) = level.at(j.try_into().unwrap()); + let node = self.tree.entry(*node_id).read(); + filled_position_in_level.append((node.value.price, node.color, *position)); + j += 1; }; + filled_position_in_levels.append(filled_position_in_level); + filled_position_in_level = ArrayTrait::new(); + i += 1; + }; return filled_position_in_levels; } @@ -347,7 +345,7 @@ pub mod RBTreeComponent { return; } - let node = self.tree.read(node_id); + let node = self.tree.entry(node_id).read(); node_positions.insert(node_id, position); @@ -382,7 +380,7 @@ pub mod RBTreeComponent { return (true, 1); // Null nodes are considered black } - let node_data = self.tree.read(node); + let node_data = self.tree.entry(node).read(); let (left_valid, left_black_height) = self.validate_node(node_data.left); let (right_valid, right_black_height) = self.validate_node(node_data.right); @@ -417,7 +415,7 @@ pub mod RBTreeComponent { > of RBTreeDeleteBalanceTrait { fn delete_node(ref self: ComponentState, delete_id: felt252) { let mut y = delete_id; - let mut node_delete: Node = self.tree.read(delete_id); + let mut node_delete: Node = self.tree.entry(delete_id).read(); let mut y_original_color = node_delete.color; let mut x: felt252 = 0; let mut x_parent: felt252 = 0; @@ -432,7 +430,7 @@ pub mod RBTreeComponent { self.transplant(delete_id, x); } else { y = self.minimum(node_delete.right); - let y_node: Node = self.tree.read(y); + let y_node: Node = self.tree.entry(y).read(); y_original_color = y_node.color; x = y_node.right; @@ -441,25 +439,25 @@ pub mod RBTreeComponent { } else { x_parent = y_node.parent; self.transplant(y, x); - let mut y_node: Node = self.tree.read(y); - node_delete = self.tree.read(delete_id); + let mut y_node: Node = self.tree.entry(y).read(); + node_delete = self.tree.entry(delete_id).read(); y_node.right = node_delete.right; - self.tree.write(y, y_node); + self.tree.entry(y).write(y_node); self.update_parent(node_delete.right, y); } self.transplant(delete_id, y); - let mut y_node: Node = self.tree.read(y); - node_delete = self.tree.read(delete_id); + let mut y_node: Node = self.tree.entry(y).read(); + node_delete = self.tree.entry(delete_id).read(); y_node.left = node_delete.left; y_node.color = node_delete.color; - self.tree.write(y, y_node); - node_delete = self.tree.read(delete_id); + self.tree.entry(y).write(y_node); + node_delete = self.tree.entry(delete_id).read(); self.update_parent(node_delete.left, y); } - self.tree.write(delete_id, self.get_default_node()); + self.tree.entry(delete_id).write(self.get_default_node()); if y_original_color == BLACK { self.delete_fixup(x, x_parent); @@ -471,98 +469,99 @@ pub mod RBTreeComponent { fn delete_fixup( ref self: ComponentState, mut x: felt252, mut x_parent: felt252 ) { - while x != self.root.read() - && (x == 0 || self.is_black(x)) { - let mut x_parent_node: Node = self.tree.read(x_parent); - if x == x_parent_node.left { - let mut w = x_parent_node.right; - - // Case 1: x's sibling w is red - if self.is_red(w) { - self.set_color(w, BLACK); - self.set_color(x_parent, RED); - self.rotate_left(x_parent); - x_parent_node = self.tree.read(x_parent); + while x != self.root.read() && (x == 0 || self.is_black(x)) { + let mut x_parent_node: Node = self.tree.entry(x_parent).read(); + if x == x_parent_node.left { + let mut w = x_parent_node.right; + + // Case 1: x's sibling w is red + if self.is_red(w) { + self.set_color(w, BLACK); + self.set_color(x_parent, RED); + self.rotate_left(x_parent); + x_parent_node = self.tree.entry(x_parent).read(); + w = x_parent_node.right; + } + + // Case 2: x's sibling w is black, and both of w's children are black + let mut w_node: Node = self.tree.entry(w).read(); + if (w_node.left == 0 || self.is_black(w_node.left)) + && (w_node.right == 0 || self.is_black(w_node.right)) { + self.set_color(w, RED); + x = x_parent; + x_parent = self.get_parent(x); + } else { + // Case 3: x's sibling w is black, w's left child is red, and w's right + // child is black + if w_node.right == 0 || self.is_black(w_node.right) { + if w_node.left != 0 { + self.set_color(w_node.left, BLACK); + } + self.set_color(w, RED); + self.rotate_right(w); + x_parent_node = self.tree.entry(x_parent).read(); w = x_parent_node.right; } - // Case 2: x's sibling w is black, and both of w's children are black - let mut w_node: Node = self.tree.read(w); - if (w_node.left == 0 || self.is_black(w_node.left)) - && (w_node.right == 0 || self.is_black(w_node.right)) { - self.set_color(w, RED); - x = x_parent; - x_parent = self.get_parent(x); - } else { - // Case 3: x's sibling w is black, w's left child is red, and w's right child is black - if w_node.right == 0 || self.is_black(w_node.right) { - if w_node.left != 0 { - self.set_color(w_node.left, BLACK); - } - self.set_color(w, RED); - self.rotate_right(w); - x_parent_node = self.tree.read(x_parent); - w = x_parent_node.right; - } + // Case 4: x's sibling w is black, and w's right child is red + x_parent_node = self.tree.entry(x_parent).read(); + self.set_color(w, x_parent_node.color); + self.set_color(x_parent, BLACK); + w_node = self.tree.entry(w).read(); + if w_node.right != 0 { + self.set_color(w_node.right, BLACK); + } + self.rotate_left(x_parent); + x = self.root.read(); + break; + } + } else { + // Mirror cases for when x is a right child + let mut w = x_parent_node.left; + + // Case 1 (mirror): x's sibling w is red + if self.is_red(w) { + self.set_color(w, BLACK); + self.set_color(x_parent, RED); + self.rotate_right(x_parent); + x_parent_node = self.tree.entry(x_parent).read(); + w = x_parent_node.left; + } - // Case 4: x's sibling w is black, and w's right child is red - x_parent_node = self.tree.read(x_parent); - self.set_color(w, x_parent_node.color); - self.set_color(x_parent, BLACK); - w_node = self.tree.read(w); + // Case 2 (mirror): x's sibling w is black, and both of w's children are black + let mut w_node: Node = self.tree.entry(w).read(); + if (w_node.right == 0 || self.is_black(w_node.right)) + && (w_node.left == 0 || self.is_black(w_node.left)) { + self.set_color(w, RED); + x = x_parent; + x_parent = self.get_parent(x); + } else { + // Case 3 (mirror): x's sibling w is black, w's right child is red, and w's + // left child is black + if w_node.left == 0 || self.is_black(w_node.left) { if w_node.right != 0 { self.set_color(w_node.right, BLACK); } - self.rotate_left(x_parent); - x = self.root.read(); - break; - } - } else { - // Mirror cases for when x is a right child - let mut w = x_parent_node.left; - - // Case 1 (mirror): x's sibling w is red - if self.is_red(w) { - self.set_color(w, BLACK); - self.set_color(x_parent, RED); - self.rotate_right(x_parent); - x_parent_node = self.tree.read(x_parent); + self.set_color(w, RED); + self.rotate_left(w); + x_parent_node = self.tree.entry(x_parent).read(); w = x_parent_node.left; } - // Case 2 (mirror): x's sibling w is black, and both of w's children are black - let mut w_node: Node = self.tree.read(w); - if (w_node.right == 0 || self.is_black(w_node.right)) - && (w_node.left == 0 || self.is_black(w_node.left)) { - self.set_color(w, RED); - x = x_parent; - x_parent = self.get_parent(x); - } else { - // Case 3 (mirror): x's sibling w is black, w's right child is red, and w's left child is black - if w_node.left == 0 || self.is_black(w_node.left) { - if w_node.right != 0 { - self.set_color(w_node.right, BLACK); - } - self.set_color(w, RED); - self.rotate_left(w); - x_parent_node = self.tree.read(x_parent); - w = x_parent_node.left; - } - - // Case 4 (mirror): x's sibling w is black, and w's left child is red - x_parent_node = self.tree.read(x_parent); - self.set_color(w, x_parent_node.color); - self.set_color(x_parent, BLACK); - w_node = self.tree.read(w); - if w_node.left != 0 { - self.set_color(w_node.left, BLACK); - } - self.rotate_right(x_parent); - x = self.root.read(); - break; + // Case 4 (mirror): x's sibling w is black, and w's left child is red + x_parent_node = self.tree.read(x_parent); + self.set_color(w, x_parent_node.color); + self.set_color(x_parent, BLACK); + w_node = self.tree.entry(w).read(); + if w_node.left != 0 { + self.set_color(w_node.left, BLACK); } + self.rotate_right(x_parent); + x = self.root.read(); + break; } - }; + } + }; // Final color adjustment if x != 0 { @@ -571,7 +570,7 @@ pub mod RBTreeComponent { } fn transplant(ref self: ComponentState, u: felt252, v: felt252) { - let u_node = self.tree.read(u); + let u_node = self.tree.entry(u).read(); if u_node.parent == 0 { self.root.write(v); } else if self.is_left_child(u) { @@ -586,10 +585,10 @@ pub mod RBTreeComponent { fn minimum(ref self: ComponentState, node_id: felt252) -> felt252 { let mut current = node_id; - let mut node: Node = self.tree.read(current); + let mut node: Node = self.tree.entry(current).read(); while node.left != 0 { current = node.left; - node = self.tree.read(current); + node = self.tree.entry(current).read(); }; current } @@ -614,14 +613,14 @@ pub mod RBTreeComponent { new_node_id: felt252, value: Bid ) { - let mut current_node: Node = self.tree.read(current_id); + let mut current_node: Node = self.tree.entry(current_id).read(); if value <= current_node.value { if current_node.left == 0 { current_node.left = new_node_id; let new_node = self.create_leaf_node(@value, current_id); - self.tree.write(new_node_id, new_node); - self.tree.write(current_id, current_node); + self.tree.entry(new_node_id).write(new_node); + self.tree.entry(current_id).write(current_node); return; } @@ -632,8 +631,8 @@ pub mod RBTreeComponent { current_node.right = new_node_id; let new_node = self.create_leaf_node(@value, current_id); - self.tree.write(new_node_id, new_node); - self.tree.write(current_id, current_node); + self.tree.entry(new_node_id).write(new_node); + self.tree.entry(current_id).write(current_node); return; } @@ -643,21 +642,19 @@ pub mod RBTreeComponent { fn balance_after_insertion(ref self: ComponentState, node_id: felt252) { let mut current = node_id; - let mut current_node: Node = self.tree.read(current); - while current != self.root.read() - && self - .is_red(current_node.parent) { - let parent = current_node.parent; - let parent_node: Node = self.tree.read(parent); - let grandparent = parent_node.parent; - - if self.is_left_child(parent) { - current = self.balance_left_case(current, parent, grandparent); - } else { - current = self.balance_right_case(current, parent, grandparent); - } - current_node = self.tree.read(current); - }; + let mut current_node: Node = self.tree.entry(current).read(); + while current != self.root.read() && self.is_red(current_node.parent) { + let parent = current_node.parent; + let parent_node: Node = self.tree.entry(parent).read(); + let grandparent = parent_node.parent; + + if self.is_left_child(parent) { + current = self.balance_left_case(current, parent, grandparent); + } else { + current = self.balance_right_case(current, parent, grandparent); + } + current_node = self.tree.entry(current).read(); + }; self.ensure_root_is_black(); } @@ -667,7 +664,7 @@ pub mod RBTreeComponent { parent: felt252, grandparent: felt252 ) -> felt252 { - let grandparent_node: Node = self.tree.read(grandparent); + let grandparent_node: Node = self.tree.entry(grandparent).read(); let uncle = grandparent_node.right; if self.is_red(uncle) { @@ -683,7 +680,7 @@ pub mod RBTreeComponent { parent: felt252, grandparent: felt252 ) -> felt252 { - let grandparent_node: Node = self.tree.read(grandparent); + let grandparent_node: Node = self.tree.entry(grandparent).read(); let uncle = grandparent_node.left; if self.is_red(uncle) { @@ -717,7 +714,7 @@ pub mod RBTreeComponent { new_current = parent; self.rotate_left(new_current); } - let mut new_current_node: Node = self.tree.read(new_current); + let mut new_current_node: Node = self.tree.entry(new_current).read(); let new_parent = new_current_node.parent; self.set_color(new_parent, BLACK); // Black self.set_color(grandparent, RED); // Red @@ -736,7 +733,7 @@ pub mod RBTreeComponent { new_current = parent; self.rotate_right(new_current); } - let new_current_node: Node = self.tree.read(new_current); + let new_current_node: Node = self.tree.entry(new_current).read(); let new_parent = new_current_node.parent; self.set_color(new_parent, BLACK); // Black self.set_color(grandparent, RED); // Red @@ -750,9 +747,9 @@ pub mod RBTreeComponent { TContractState, +HasComponent > of RBTreeRotationsTrait { fn rotate_right(ref self: ComponentState, y: felt252) -> felt252 { - let mut y_node: Node = self.tree.read(y); + let mut y_node: Node = self.tree.entry(y).read(); let x = y_node.left; - let x_node: Node = self.tree.read(x); + let x_node: Node = self.tree.entry(x).read(); let B = x_node.right; // Perform rotation @@ -761,7 +758,7 @@ pub mod RBTreeComponent { // Update parent pointers // Is read again required? - y_node = self.tree.read(y); + y_node = self.tree.entry(y).read(); let y_parent = y_node.parent; self.update_parent(x, y_parent); self.update_parent(y, x); @@ -773,14 +770,14 @@ pub mod RBTreeComponent { if y_parent == 0 { self.root.write(x); } else { - let mut parent: Node = self.tree.read(y_parent); + let mut parent: Node = self.tree.entry(y_parent).read(); if parent.left == y { parent.left = x; } else { parent.right = x; } - self.tree.write(y_parent, parent); + self.tree.entry(y_parent).write(parent); } // Return the new root of the subtree @@ -798,7 +795,7 @@ pub mod RBTreeComponent { self.update_right(x, B); // Update parent pointers - x_node = self.tree.read(x); + x_node = self.tree.entry(x).read(); let x_parent = x_node.parent; self.update_parent(y, x_parent); self.update_parent(x, y); @@ -810,13 +807,13 @@ pub mod RBTreeComponent { if x_parent == 0 { self.root.write(y); } else { - let mut parent: Node = self.tree.read(x_parent); + let mut parent: Node = self.tree.entry(x_parent).read(); if parent.left == x { parent.left = y; } else { parent.right = y; } - self.tree.write(x_parent, parent); + self.tree.entry(x_parent).write(parent); } // Return the new root of the subtree diff --git a/src/library/utils.cairo b/src/library/utils.cairo index abab5b2c..5a71bec2 100644 --- a/src/library/utils.cairo +++ b/src/library/utils.cairo @@ -1,5 +1,15 @@ -use pitch_lake_starknet::types::{VaultType, Consts::BPS}; +use core::poseidon::poseidon_hash_span; +use pitch_lake::vault::interface::L1DataRequest; +pub const VALUES_NOT_IN_RANGE: felt252 = 'Values not in range'; + +fn assert_equal_in_range, +Add, +Sub, +Drop, +Copy>( + a: T, b: T, range: T +) { + assert(a >= b - range && a <= b + range, VALUES_NOT_IN_RANGE); +} + +// @dev Returns the minimum of a and b fn min, +PartialOrd, +Drop, +Copy>(a: T, b: T) -> T { match a < b { true => a, @@ -7,7 +17,7 @@ fn min, +PartialOrd, +Drop, +Copy>(a: T, b: T) -> T { } } -// Get the maximum of two values +// @dev Returns the maximum of a and b fn max, +PartialOrd, +Drop, +Copy>(a: T, b: T) -> T { match a < b { true => b, @@ -15,7 +25,7 @@ fn max, +PartialOrd, +Drop, +Copy>(a: T, b: T) -> T { } } -// Raise x to the y power +// @dev Returns base^exp fn pow(base: u256, exp: u8) -> u256 { if exp == 0 { 1 @@ -28,25 +38,10 @@ fn pow(base: u256, exp: u8) -> u256 { } } -// Calculate a round's strike price -// @param vault_type: The type of the vault -// @param avg_basefee: The TWAP of the basefee for the last 2 weeks -// @param volatility: The volatility of the basefee for the last 2 weeks -// @return The strike price of the upcoming round -fn calculate_strike_price(vault_type: VaultType, avg_basefee: u256, volatility: u128) -> u256 { - let adjustment = (avg_basefee * volatility.into()) / BPS; - match vault_type { - VaultType::AtTheMoney(_) => avg_basefee, - VaultType::OutOfMoney(_) => avg_basefee + adjustment, - VaultType::InTheMoney(_) => { - // @note 0 ensures payout for round, need to decide if we should use ATM price, - // or scale the adjustment to keep ITM nature but not a 0 strike - if adjustment >= avg_basefee { - avg_basefee - } else { - avg_basefee - adjustment - } - }, - } +// @dev Serialize the job request and hash it to create the its ID +fn generate_request_id(request: L1DataRequest) -> felt252 { + let mut serialized: Array = Default::default(); + request.serialize(ref serialized); + poseidon_hash_span(serialized.span()) } diff --git a/src/market_aggregator/contract.cairo b/src/market_aggregator/contract.cairo deleted file mode 100644 index 63d7caa5..00000000 --- a/src/market_aggregator/contract.cairo +++ /dev/null @@ -1,136 +0,0 @@ -#[starknet::contract] -mod MarketAggregator { - use core::integer::BoundedInt; - use core::array::ArrayTrait; - use starknet::ContractAddress; - use super::super::interface::{IMarketAggregator, IMarketAggregatorMock}; - use super::super::types::{PeriodTypes::{TIME, BLOCK}, Errors}; - use pitch_lake_starknet::vault::interface::{IVaultDispatcher, IVaultDispatcherTrait}; - - /// ************************************************************************* - /// STORAGE - /// ************************************************************************* - #[storage] - struct Storage { - cap_levels: LegacyMap<(felt252, u64, u64), (bool, u128)>, - reserve_prices: LegacyMap<(felt252, u64, u64), (bool, u256)>, - strike_prices: LegacyMap<(felt252, u64, u64), (bool, u256)>, - TWAPs: LegacyMap<(felt252, u64, u64), (bool, u256)>, - vols: LegacyMap<(felt252, u64, u64), (bool, u128)>, - reserve_prices_for_rounds: LegacyMap<(ContractAddress, u256), (bool, u256)>, - volatility_for_rounds: LegacyMap<(ContractAddress, u256), (bool, u128)>, - cap_level_for_rounds: LegacyMap<(ContractAddress, u256), (bool, u128)>, - } - - // ************************************************************************* - // Constructor - // ************************************************************************* - #[constructor] - fn constructor(ref self: ContractState) {} - - /// ************************************************************************* - /// IMPLEMENTATION - /// ************************************************************************* - /// Set data using a single function - #[abi(embed_v0)] - impl MarketAggregatorImpl of IMarketAggregator { - fn get_TWAP_for_block_period(self: @ContractState, from: u64, to: u64) -> Option { - let (is_set, TWAP) = self.TWAPs.read((BLOCK, from, to)); - match is_set { - true => Option::Some(TWAP), - false => Option::None - } - } - - fn get_TWAP_for_time_period(self: @ContractState, from: u64, to: u64) -> Option { - let (is_set, TWAP) = self.TWAPs.read((TIME, from, to)); - match is_set { - true => Option::Some(TWAP), - false => Option::None - } - } - - fn get_reserve_price_for_round( - self: @ContractState, vault_address: ContractAddress, round_id: u256 - ) -> Option { - let (is_set, reserve_price) = self - .reserve_prices_for_rounds - .read((vault_address, round_id)); - match is_set { - true => Option::Some(reserve_price), - false => Option::None - } - } - - fn get_volatility_for_round( - self: @ContractState, vault_address: ContractAddress, round_id: u256 - ) -> Option { - let (is_set, vol) = self.volatility_for_rounds.read((vault_address, round_id)); - match is_set { - true => Option::Some(vol), - false => Option::None - } - } - - - fn get_cap_level_for_round( - self: @ContractState, vault_address: ContractAddress, round_id: u256 - ) -> Option { - let (is_set, cap_level) = self.cap_level_for_rounds.read((vault_address, round_id)); - match is_set { - true => Option::Some(cap_level), - false => Option::None - } - } - } - - /// ************************************************************************* - /// MOCK IMPLEMENTATION - /// ************************************************************************* - /// Entry points to set the data for testing as a single function - #[abi(embed_v0)] - impl MarketAggregatorMock of IMarketAggregatorMock { - /// TWAP /// - fn set_TWAP_for_block_period(ref self: ContractState, from: u64, to: u64, TWAP: u256) { - let (is_set, _) = self.TWAPs.read((BLOCK, from, to)); - //assert(!is_set, Errors::VALUE_ALREADY_SET); - self.TWAPs.write((BLOCK, from, to), (true, TWAP)); - } - - fn set_TWAP_for_time_period(ref self: ContractState, from: u64, to: u64, TWAP: u256) { - let (is_set, _) = self.TWAPs.read((TIME, from, to)); - //assert(!is_set, Errors::VALUE_ALREADY_SET); - self.TWAPs.write((TIME, from, to), (true, TWAP)); - } - - /// RESERVE PRICE /// - /// VOLATILITY /// - /// CAP LEVEL /// - fn set_reserve_price_for_round( - ref self: ContractState, - vault_address: ContractAddress, - round_id: u256, - reserve_price: u256 - ) { - let (is_set, _) = self.reserve_prices_for_rounds.read((vault_address, round_id)); - //assert(!is_set, Errors::VALUE_ALREADY_SET); - self.reserve_prices_for_rounds.write((vault_address, round_id), (true, reserve_price)); - } - - fn set_volatility_for_round( - ref self: ContractState, vault_address: ContractAddress, round_id: u256, vol: u128 - ) { - let (is_set, _) = self.volatility_for_rounds.read((vault_address, round_id)); - //assert(!is_set, Errors::VALUE_ALREADY_SET); - self.volatility_for_rounds.write((vault_address, round_id), (true, vol)); - } - - fn set_cap_level_for_round( - ref self: ContractState, vault_address: ContractAddress, round_id: u256, cap_level: u128 - ) { - let (is_set, _) = self.cap_level_for_rounds.read((vault_address, round_id)); - //assert(!is_set, Errors::VALUE_ALREADY_SET); - self.cap_level_for_rounds.write((vault_address, round_id), (true, cap_level)); - } - } -} diff --git a/src/market_aggregator/interface.cairo b/src/market_aggregator/interface.cairo deleted file mode 100644 index 2cfa1aca..00000000 --- a/src/market_aggregator/interface.cairo +++ /dev/null @@ -1,54 +0,0 @@ -/// ************************************************************************* -/// CORE -/// ************************************************************************* -#[starknet::interface] -trait IMarketAggregator { - /// TWAP /// - fn get_TWAP_for_block_period(self: @TContractState, from: u64, to: u64) -> Option; - fn get_TWAP_for_time_period(self: @TContractState, from: u64, to: u64) -> Option; - - /// RESERVE PRICE /// - /// VOLATILITY /// - /// CAP LEVEL /// - fn get_reserve_price_for_round( - self: @TContractState, vault_address: starknet::ContractAddress, round_id: u256 - ) -> Option; - fn get_volatility_for_round( - self: @TContractState, vault_address: starknet::ContractAddress, round_id: u256 - ) -> Option; - fn get_cap_level_for_round( - self: @TContractState, vault_address: starknet::ContractAddress, round_id: u256 - ) -> Option; -} - -/// ************************************************************************* -/// MOCK -/// ************************************************************************* -#[starknet::interface] -trait IMarketAggregatorMock { - /// TWAP /// - fn set_TWAP_for_block_period(ref self: TContractState, from: u64, to: u64, TWAP: u256); - fn set_TWAP_for_time_period(ref self: TContractState, from: u64, to: u64, TWAP: u256); - - /// RESERVE PRICE /// - /// VOLATILITY /// - /// CAP LEVEL /// - fn set_reserve_price_for_round( - ref self: TContractState, - vault_address: starknet::ContractAddress, - round_id: u256, - reserve_price: u256 - ); - fn set_volatility_for_round( - ref self: TContractState, - vault_address: starknet::ContractAddress, - round_id: u256, - vol: u128 - ); - fn set_cap_level_for_round( - ref self: TContractState, - vault_address: starknet::ContractAddress, - round_id: u256, - cap_level: u128 - ); -} diff --git a/src/market_aggregator/types.cairo b/src/market_aggregator/types.cairo deleted file mode 100644 index 98ff36ef..00000000 --- a/src/market_aggregator/types.cairo +++ /dev/null @@ -1,15 +0,0 @@ -mod PeriodTypes { - const TIME: felt252 = 'TIME'; - const BLOCK: felt252 = 'BLOCK'; -} - -mod DataTypes { - const RESERVE_PRICE: felt252 = 'RESERVE PRICE'; - const CAP_LEVEL: felt252 = 'CAP LEVEL'; - const TWAP: felt252 = 'TWAP'; -} - -mod Errors { - const VALUE_ALREADY_SET: felt252 = 'Value already set in storage'; -} - diff --git a/src/option_round/contract.cairo b/src/option_round/contract.cairo index ca409417..8396dad9 100644 --- a/src/option_round/contract.cairo +++ b/src/option_round/contract.cairo @@ -1,75 +1,68 @@ #[starknet::contract] mod OptionRound { - use openzeppelin::token::erc20::{ - ERC20Component, interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait, IERC20Metadata}, + use starknet::{get_block_timestamp, get_caller_address, get_contract_address, ContractAddress,}; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess,}; + use starknet::storage::{Map}; + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use openzeppelin_token::erc20::interface::{ + ERC20ABIDispatcher, ERC20ABIDispatcherTrait, IERC20Metadata }; - 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, VaultType, Consts::BPS, - }, + use pitch_lake::vault::interface::{IVaultDispatcher, IVaultDispatcherTrait}; + use pitch_lake::option_round::interface::{ + PricingData, ConstructorArgs, IOptionRound, OptionRoundState + }; + use pitch_lake::types::{Bid, Consts::BPS,}; + use pitch_lake::library::utils::{max, min}; + use pitch_lake::library::pricing_utils::{ + calculate_total_options_available, calculate_payout_per_option, + }; + use pitch_lake::library::red_black_tree::{RBTreeComponent, RBTreeComponent::Node}; + use pitch_lake::library::constants::{ + ROUND_TRANSITION_PERIOD, AUCTION_RUN_TIME, OPTION_RUN_TIME }; - use starknet::{get_block_timestamp, get_caller_address, get_contract_address, ContractAddress,}; - // ERC20 Component + // ************************************************************************* + // COMPONENTS + // ************************************************************************* + component!(path: ERC20Component, storage: erc20, event: ERC20Event); - // Exposes snake_case & CamelCase entry points + component!(path: RBTreeComponent, storage: bids_tree, event: BidTreeEvent); + #[abi(embed_v0)] impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; - // Allows the contract access to internal functions - impl ERC20InternalImpl = ERC20Component::InternalImpl; - // RedBlackTree component - component!(path: RBTreeComponent, storage: bids_tree, event: BidTreeEvent); - impl RBTreeImpl = RBTreeComponent::RBTreeImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; impl RBTreeOptionRoundImpl = RBTreeComponent::RBTreeOptionRoundImpl; + impl RBTreeImpl = RBTreeComponent::RBTreeImpl; // ************************************************************************* // STORAGE // ************************************************************************* - // Note: Write description of any storage variable here-> - // @market_aggregator: The address of the contract to fetch fossil values from - // @state: The state of the option round - // @round_id: The round's id - // @cap_level: The cap level of the potential payout - // @reserve_price: The minimum bid price per option - // @strike_price: The strike price of the options - // @starting_liquidity: The amount of liquidity this round starts with (locked upon auction starting) - // @payout_per_option: The amount the option round pays out per option upon settlement - // @auction_start_date: The auction start date - // @auction_end_date: The auction end date - // @option_settlement_date: The option settlement date - // @constructor:params: Params to pass at option round creation, to be set by fossil - // @bidder_nonces: A mapping of address to u256, tells the current nonce for an address, allows tracking of bids for each user and used to create unique bid id's for each bid - // @bids_tree: Storage for the bids tree, see rb-tree-component - // @erc20: Storage for erc20 component of the round. + #[storage] struct Storage { /// vault_address: ContractAddress, state: OptionRoundState, round_id: u256, - /// - cap_level: u128, - reserve_price: u256, - strike_price: u256, + deployment_date: u64, + auction_start_date: u64, + auction_end_date: u64, + option_settlement_date: u64, /// starting_liquidity: u256, unsold_liquidity: u256, settlement_price: u256, payout_per_option: u256, /// - auction_start_date: u64, - auction_end_date: u64, - option_settlement_date: u64, + pricing_data: PricingData, /// - account_bid_nonce: LegacyMap, - has_minted: LegacyMap, - has_refunded: LegacyMap, + account_bid_nonce: Map, + has_minted: Map, + has_refunded: Map, /// #[substorage(v0)] bids_tree: RBTreeComponent::Storage, @@ -77,43 +70,71 @@ mod OptionRound { erc20: ERC20Component::Storage, } + // ************************************************************************* + // Errors + // ************************************************************************* + + mod Errors { + const CallerIsNotVault: felt252 = 'Caller not the Vault'; + // Starting an auction + const PricingDataNotSet: felt252 = 'Pricing data not set'; + const AuctionStartDateNotReached: felt252 = 'Auction start date not reached'; + const AuctionAlreadyStarted: felt252 = 'Auction already started'; + // Ending an auction + const AuctionEndDateNotReached: felt252 = 'Auction end date not reached'; + const AuctionAlreadyEnded: felt252 = 'Auction has already ended'; + // Settling an option round + const OptionSettlementDateNotReached: felt252 = 'Settlement date not reached'; + const OptionRoundAlreadySettled: felt252 = 'Option round already settled'; + // Bidding & upating bids + const BiddingWhileNotAuctioning: felt252 = 'Can only bid while auctioning'; + const BidAmountZero: felt252 = 'Bid amount cannot be 0'; + const BidBelowReservePrice: felt252 = 'Bid price below reserve price'; + const CallerNotBidOwner: felt252 = 'Caller is not bid owner'; + const BidMustBeIncreased: felt252 = 'Bid updates must increase price'; + // Refunding bids & tokenizing options + const AuctionNotEnded: felt252 = 'Auction has not ended yet'; + const OptionRoundNotSettled: felt252 = 'Option round not settled yet'; + /// Internal Errors /// + const BidsShouldNotHaveSameTreeNonce: felt252 = 'Tree nonces should be unique'; + } + // ************************************************************************* // Constructor // ************************************************************************* + #[constructor] - fn constructor( - ref self: ContractState, - vault_address: ContractAddress, - round_id: u256, - auction_start_date: u64, - auction_end_date: u64, - option_settlement_date: u64, - reserve_price: u256, - cap_level: u128, - strike_price: u256 - ) { - self.state.write(OptionRoundState::Open); - self.vault_address.write(vault_address); - self.round_id.write(round_id); - // @dev Set round params - self.reserve_price.write(reserve_price); - self.cap_level.write(cap_level); - self.strike_price.write(strike_price); - self.auction_start_date.write(auction_start_date); - self.auction_end_date.write(auction_end_date); - self.option_settlement_date.write(option_settlement_date); + fn constructor(ref self: ContractState, args: ConstructorArgs) { + // @dev Get the constructor arguments + let ConstructorArgs { vault_address, round_id, pricing_data, } = args; + // @dev Set the name and symbol for the minted option (ERC-20) tokens let (name, symbol) = self.generate_erc20_name_and_symbol(round_id); self.erc20.initializer(name, symbol); - } + // @dev Set round's dates + let deployment_date = get_block_timestamp(); + let (auction_start_date, auction_end_date, settlement_date) = self + .calculate_dates(deployment_date); + self.deployment_date.write(get_block_timestamp()); + self.auction_start_date.write(auction_start_date); + self.auction_end_date.write(auction_end_date); + self.option_settlement_date.write(settlement_date); + + // @dev Set rest of round params + self.vault_address.write(vault_address); + self.round_id.write(round_id); + self.pricing_data.write(pricing_data); + } // ************************************************************************* // EVENTS // ************************************************************************* + #[event] #[derive(Drop, starknet::Event, PartialEq)] enum Event { + PricingDataSet: PricingDataSet, AuctionStarted: AuctionStarted, BidPlaced: BidPlaced, BidUpdated: BidUpdated, @@ -128,6 +149,13 @@ mod OptionRound { ERC20Event: ERC20Component::Event, } + #[derive(Drop, starknet::Event, PartialEq)] + struct PricingDataSet { + strike_price: u256, + cap_level: u128, + reserve_price: u256, + } + // @dev Emitted when the auction starts // @member starting_liquidity: The liquidity locked at the start of the auction // @member options_available: The max number of options that can sell in the auction @@ -221,8 +249,9 @@ mod OptionRound { } // ************************************************************************* - // IMPLEMENTATION + // IMPLEMENTATIONS // ************************************************************************* + #[abi(embed_v0)] impl ERC20MetadataImpl of IERC20Metadata { fn name(self: @ContractState) -> ByteArray { @@ -259,6 +288,10 @@ mod OptionRound { self.state.read() } + fn get_deployment_date(self: @ContractState) -> u64 { + self.deployment_date.read() + } + fn get_auction_start_date(self: @ContractState) -> u64 { self.auction_start_date.read() } @@ -280,15 +313,15 @@ mod OptionRound { } fn get_reserve_price(self: @ContractState) -> u256 { - self.reserve_price.read() + self.pricing_data.reserve_price.read() } fn get_strike_price(self: @ContractState) -> u256 { - self.strike_price.read() + self.pricing_data.strike_price.read() } fn get_cap_level(self: @ContractState) -> u128 { - self.cap_level.read() + self.pricing_data.cap_level.read() } fn get_options_available(self: @ContractState) -> u256 { @@ -354,7 +387,8 @@ mod OptionRound { return 0; } - // @dev Get the account's winning bids, losing bids, and clearing bid if the account owns it + // @dev Get the account's winning bids, losing bids, and clearing bid if the account + // owns it let (mut winning_bids, mut losing_bids, clearing_bid_maybe) = self .calculate_bid_outcome_for(account); @@ -363,7 +397,8 @@ mod OptionRound { match clearing_bid_maybe { Option::None => {}, Option::Some(bid) => { - // @dev Only the clearing_bid can be partially sold, the clearing_bid_amount_sold is saved in the tree + // @dev Only the clearing_bid can be partially sold, the + // clearing_bid_amount_sold is saved in the tree let options_sold = self.bids_tree.clearing_bid_amount_sold.read(); let options_not_sold = bid.amount - options_sold; refundable_balance += options_not_sold * bid.price; @@ -402,14 +437,16 @@ mod OptionRound { return 0; } - // @dev Get the account's winning bids, losing bids, and clearing bid if the account owns it + // @dev Get the account's winning bids, losing bids, and clearing bid if the account + // owns it let (mut winning_bids, _, clearing_bid_maybe) = self.calculate_bid_outcome_for(account); // @dev Add mintable balance from the clearing bid let mut mintable_balance = 0; match clearing_bid_maybe { Option::Some(_) => { - // @dev The clearing bid potentially sells < the total options bid for, so it is stored separately + // @dev The clearing bid potentially sells < the total options bid for, so it is + // stored separately let options_sold = self.bids_tree.clearing_bid_amount_sold.read(); mintable_balance += options_sold; }, @@ -428,13 +465,14 @@ mod OptionRound { } fn get_account_total_options(self: @ContractState, account: ContractAddress) -> u256 { - // @dev An account's total options is their mintable balance plus any option ERC20 tokens - // the already own - self.get_account_mintable_options(account) + self.erc20.ERC20_balances.read(account) + // @dev An account's total options is their mintable balance plus any option ERC20 + // tokens the already own + self.get_account_mintable_options(account) + self.erc20.balance_of(account) } fn get_account_payout_balance(self: @ContractState, account: ContractAddress) -> u256 { - // @dev An account's payout balance is their total options multiplied by the payout per option + // @dev An account's payout balance is their total options multiplied by the payout per + // option let number_of_options = self.get_account_total_options(account); let payout_per_option = self.payout_per_option.read(); number_of_options * payout_per_option @@ -447,33 +485,44 @@ mod OptionRound { /// State transition - fn update_round_params( - ref self: ContractState, reserve_price: u256, cap_level: u128, strike_price: u256 - ) { + fn set_pricing_data(ref self: ContractState, pricing_data: PricingData) { + // @dev Assert the caller is the vault self.assert_caller_is_vault(); - self.assert_params_can_update(); - self.reserve_price.write(reserve_price); - self.cap_level.write(cap_level); - self.strike_price.write(strike_price); + // @dev Assert the auction has not started yet + assert(self.state.read() == OptionRoundState::Open, Errors::AuctionAlreadyStarted); + + // @dev Assert pricing data is not 0 + assert(pricing_data != Default::::default(), 'REPLACE ME'); + + // @dev Set the pricing data points + self.pricing_data.write(pricing_data); + + // @dev Emit event + let PricingData { strike_price, cap_level, reserve_price } = pricing_data; + self + .emit( + Event::PricingDataSet(PricingDataSet { strike_price, cap_level, reserve_price }) + ); } fn start_auction(ref self: ContractState, starting_liquidity: u256) -> u256 { - self.assert_caller_is_vault(); - self.assert_auction_can_start(); - + // @dev Ensure pricing data is set + let pricing_data = self.pricing_data.read(); + assert(pricing_data != Default::default(), Errors::PricingDataNotSet); // @dev Calculate total options available - let strike_price = self.strike_price.read(); - let cap_level = self.cap_level.read(); - let options_available = self - .calculate_total_options_available(starting_liquidity, strike_price, cap_level); + let strike_price = pricing_data.strike_price; + let cap_level = pricing_data.cap_level; + let options_available = calculate_total_options_available( + starting_liquidity, strike_price, cap_level + ); - // @dev Write auction params to storage & update state + // @dev Write auction params to storage self.starting_liquidity.write(starting_liquidity); self.bids_tree.total_options_available.write(options_available); - self.set_state(OptionRoundState::Auctioning); - // @dev Emit auction started event + // @dev Transition state and emit event + self.transition_state_to(OptionRoundState::Auctioning); self .emit( Event::AuctionStarted(AuctionStarted { starting_liquidity, options_available }) @@ -484,28 +533,24 @@ mod OptionRound { } fn end_auction(ref self: ContractState) -> (u256, u256) { - self.assert_caller_is_vault(); - self.assert_auction_can_end(); - - // @dev Calculate how many options sell and the price per each option + // @dev Calculate how many options were sold and the price per one let options_available = self.bids_tree._get_total_options_available(); - let (clearing_price, options_sold) = self.update_clearing_price(); + let (clearing_price, options_sold) = self.bids_tree.find_clearing_price(); - // @dev Update unsold liquidity if some options do not sell + // @dev Set unsold liquidity if some options do not sell let starting_liq = self.starting_liquidity.read(); - let sold_liq = (starting_liq * options_sold) / options_available; - let unsold_liquidity = starting_liq - sold_liq; + let options_unsold = options_available - options_sold; + let unsold_liquidity = (starting_liq * options_unsold) / options_available; + if unsold_liquidity.is_non_zero() { self.unsold_liquidity.write(unsold_liquidity); } - // @dev Send premiums to the vault + // @dev Send premiums to Vault self.get_eth_dispatcher().transfer(self.vault_address.read(), self.get_total_premium()); - // @dev Update state to Running - self.set_state(OptionRoundState::Running); - - // @dev Emit auction ended event + // @dev Transition state and emit event + self.transition_state_to(OptionRoundState::Running); self .emit( Event::AuctionEnded( @@ -518,23 +563,19 @@ mod OptionRound { } fn settle_round(ref self: ContractState, settlement_price: u256) -> u256 { - self.assert_caller_is_vault(); - self.assert_round_can_settle(); - // @dev Calculate payout per option - let strike_price = self.get_strike_price(); - let cap_level = self.get_cap_level().into(); - let payout_per_option = self - .calculate_payout_per_option(strike_price, cap_level, settlement_price); + let strike_price = self.pricing_data.strike_price.read(); + let cap_level = self.pricing_data.cap_level.read(); + let payout_per_option = calculate_payout_per_option( + strike_price, cap_level, settlement_price + ); // @dev Set payout per option and settlement price self.payout_per_option.write(payout_per_option); self.settlement_price.write(settlement_price); - // @dev Update state to Settled - self.set_state(OptionRoundState::Settled); - - // @dev Emit option settled event + // @dev Transition state and emit event + self.transition_state_to(OptionRoundState::Settled); self .emit( Event::OptionRoundSettled( @@ -549,32 +590,34 @@ mod OptionRound { /// Account functions fn place_bid(ref self: ContractState, amount: u256, price: u256) -> Bid { - self.assert_bidding_during_an_auction(); + // @dev Assert auction still on-going + self.assert_bid_can_be_placed(); - // @dev Assert bid is for more than 0 options + // @dev Assert bid amount is for more than 0 options and the bid price is + // at or above the reserve price assert(amount.is_non_zero(), Errors::BidAmountZero); - - // @dev Assert bid price is at or above reserve price assert(price >= self.get_reserve_price(), Errors::BidBelowReservePrice); - // @dev Insert bid into bids tree + // @dev Create Bid struct let account = get_caller_address(); let account_bid_nonce = self.account_bid_nonce.read(account); let bid_id = self.create_bid_id(account, account_bid_nonce); let tree_nonce = self.bids_tree.tree_nonce.read(); let bid = Bid { bid_id, owner: account, amount, price, tree_nonce }; + + // @dev Insert bid into bids tree self.bids_tree._insert(bid); // @dev Update bidder's nonce self.account_bid_nonce.write(account, account_bid_nonce + 1); - // @dev Transfer bid total from caller to this contract + // @dev Transfer bid total from account to this contract let transfer_amount = amount * price; self .get_eth_dispatcher() .transfer_from(account, get_contract_address(), transfer_amount); - // @dev Emit bid accepted event + // @dev Emit bid placed event self .emit( Event::BidPlaced( @@ -584,12 +627,13 @@ mod OptionRound { ) ); - // @return The created bid + // @dev Return the created Bid struct bid } fn update_bid(ref self: ContractState, bid_id: felt252, price_increase: u256) -> Bid { - self.assert_bidding_during_an_auction(); + // @dev Assert auction still on-going + self.assert_bid_can_be_placed(); // @dev Assert caller owns the bid let account = get_caller_address(); @@ -608,8 +652,7 @@ mod OptionRound { self.bids_tree._insert(edited_bid); // @dev Charge the difference - let bid_amount = edited_bid.amount; - let difference = bid_amount * price_increase; + let difference = edited_bid.amount * price_increase; self.get_eth_dispatcher().transfer_from(account, get_contract_address(), difference); // @dev Emit bid updated event @@ -627,7 +670,8 @@ mod OptionRound { } fn refund_unused_bids(ref self: ContractState, account: ContractAddress) -> u256 { - self.assert_auction_ended(); + // @dev Assert the auction has ended + self.assert_auction_over(); // @dev Get the total refundable balance for the account let refunded_amount = self.get_account_refundable_balance(account); @@ -646,7 +690,8 @@ mod OptionRound { } fn mint_options(ref self: ContractState) -> u256 { - self.assert_auction_ended(); + // @dev Assert the auction has ended + self.assert_auction_over(); // @dev Get the total mintable balance for the account let account = get_caller_address(); @@ -656,7 +701,7 @@ mod OptionRound { self.has_minted.write(account, true); // @dev Mint option ERC-20 tokens to the account - self.erc20._mint(account, minted_amount); + self.erc20.mint(account, minted_amount); // @dev Emit options minted event self.emit(Event::OptionsMinted(OptionsMinted { account, minted_amount })); @@ -666,21 +711,22 @@ mod OptionRound { } fn exercise_options(ref self: ContractState) -> u256 { + // @dev Assert the round has settled self.assert_round_settled(); // @dev Get the account's total option balance let account = get_caller_address(); let mut number_of_options = 0; - let mintable_amount = self.get_account_mintable_options(account); - let erc20_option_balance = self.erc20.ERC20_balances.read(account); // @dev Burn the ERC-20 options + let erc20_option_balance = self.erc20.ERC20_balances.read(account); if erc20_option_balance > 0 { number_of_options += erc20_option_balance; - self.erc20._burn(account, erc20_option_balance); + self.erc20.burn(account, erc20_option_balance); } // @dev Update the account's has minted status + let mintable_amount = self.get_account_mintable_options(account); number_of_options += mintable_amount; self.has_minted.write(account, true); @@ -704,49 +750,66 @@ mod OptionRound { // ************************************************************************* // INTERNAL FUNCTIONS // ************************************************************************* + #[generate_trait] impl InternalImpl of OptionRoundInternalTrait { - /// Assertions + fn calculate_dates(self: @ContractState, deployment_date: u64) -> (u64, u64, u64) { + let auction_start_date = deployment_date + ROUND_TRANSITION_PERIOD; + let auction_end_date = auction_start_date + AUCTION_RUN_TIME; + let option_settlement_date = auction_end_date + OPTION_RUN_TIME; - // @dev Assert that the caller is the Vault - fn assert_caller_is_vault(self: @ContractState) { - assert(get_caller_address() == self.vault_address.read(), Errors::CallerIsNotVault); + (auction_start_date, auction_end_date, option_settlement_date) } - // @dev Assert if the round's params can be updated - fn assert_params_can_update(ref self: ContractState) { - let state = self.get_state(); - let now = get_block_timestamp(); - let auction_start_date = self.get_auction_start_date(); - - assert( - state == OptionRoundState::Open && now < auction_start_date, - Errors::AuctionAlreadyStarted - ); - } + // @dev Transitions the round's state to `to_state` if the proper conditions are met + fn transition_state_to(ref self: ContractState, to_state: OptionRoundState) { + // @dev Assert the caller is the vault + self.assert_caller_is_vault(); - // @dev An auction can only start if the current time is greater than the auction start date, - // and if the round is in the Open state - fn assert_auction_can_start(self: @ContractState) { - let state = self.get_state(); + // @dev Ensure target date has been reached and the current state aligns with + // the state being transitioned to let now = get_block_timestamp(); - let auction_start_date = self.get_auction_start_date(); - assert(now >= auction_start_date, Errors::AuctionStartDateNotReached); - assert(state == OptionRoundState::Open, Errors::AuctionAlreadyStarted); + let current_state = self.get_state(); + match to_state { + // @dev Transitioning from Open to Auctioning + OptionRoundState::Auctioning => { + let target = self.get_auction_start_date(); + assert(now >= target, Errors::AuctionStartDateNotReached); + assert(current_state == OptionRoundState::Open, Errors::AuctionAlreadyStarted); + self.state.write(to_state); + }, + // @dev Transitioning from Auctioning to Running + OptionRoundState::Running => { + let target = self.get_auction_end_date(); + assert(now >= target, Errors::AuctionEndDateNotReached); + assert( + current_state == OptionRoundState::Auctioning, Errors::AuctionAlreadyEnded + ); + self.state.write(to_state); + }, + // @dev Transitioning from Running to Settled + OptionRoundState::Settled => { + let target = self.get_option_settlement_date(); + assert(now >= target, Errors::OptionSettlementDateNotReached); + assert( + current_state == OptionRoundState::Running, + Errors::OptionRoundAlreadySettled + ); + self.state.write(to_state); + }, + _ => {}, + }; } - // @dev An auction can only end if the current time is greater than the auction end date, - // and if the round is in the Auctioning state - fn assert_auction_can_end(self: @ContractState) { - let state = self.get_state(); - let now = get_block_timestamp(); - let auction_end_date = self.get_auction_end_date(); - assert(now >= auction_end_date, Errors::AuctionEndDateNotReached); - assert(state == OptionRoundState::Auctioning, Errors::AuctionAlreadyEnded); + /// Assertions /// + + // @dev Assert that the caller is the Vault + fn assert_caller_is_vault(self: @ContractState) { + assert(get_caller_address() == self.vault_address.read(), Errors::CallerIsNotVault); } // @dev Assert the auction has ended - fn assert_auction_ended(self: @ContractState) { + fn assert_auction_over(self: @ContractState) { let state = self.get_state(); assert( state == OptionRoundState::Running || state == OptionRoundState::Settled, @@ -754,28 +817,20 @@ mod OptionRound { ); } - // @dev A round can only settle if the current time is greater than the option settlement date, - // and if the round is in the Running state - fn assert_round_can_settle(self: @ContractState) { - let state = self.get_state(); - let now = get_block_timestamp(); - let settlement_date = self.get_option_settlement_date(); - assert(now >= settlement_date, Errors::OptionSettlementDateNotReached); - assert(state == OptionRoundState::Running, Errors::OptionRoundNotSettled); - } - // @dev Assert the round has settled fn assert_round_settled(self: @ContractState) { assert(self.get_state() == OptionRoundState::Settled, Errors::OptionRoundNotSettled); } - // @dev A bid can only be placed during the auction - fn assert_bidding_during_an_auction(self: @ContractState) { - let now = get_block_timestamp(); - let auction_end_date = self.get_auction_end_date(); + // @dev Assert a bid is being placed during an auction + fn assert_bid_can_be_placed(self: @ContractState) { let state = self.get_state(); - assert(now < auction_end_date, Errors::BiddingWhileNotAuctioning); - assert(state == OptionRoundState::Auctioning, Errors::BiddingWhileNotAuctioning); + let now = get_block_timestamp(); + let target = self.get_auction_end_date(); + assert( + now < target && state == OptionRoundState::Auctioning, + Errors::BiddingWhileNotAuctioning + ); } /// ERC-20 @@ -786,6 +841,7 @@ mod OptionRound { ) -> (ByteArray, ByteArray) { let name: ByteArray = format!("Pitch Lake Option Round {round_id}"); let symbol: ByteArray = format!("PLOR{round_id}"); + (name, symbol) } @@ -793,22 +849,14 @@ mod OptionRound { fn get_eth_dispatcher(self: @ContractState) -> ERC20ABIDispatcher { let vault = self.get_vault_dispatcher(); let eth_address = vault.get_eth_address(); + ERC20ABIDispatcher { contract_address: eth_address } } /// Round helpers - // @dev Update the state of the round - fn set_state(ref self: ContractState, state: OptionRoundState) { - self.state.write(state); - } - - // @dev Calculate the clearing price and total options sold from the auction - fn update_clearing_price(ref self: ContractState) -> (u256, u256) { - self.bids_tree.find_clearing_price() - } - - // @dev Get an account's winning bids, losing bids, and the clearing bid if the account owns it + // @dev Get an account's winning bids, losing bids, and the clearing bid if the account owns + // it fn calculate_bid_outcome_for( self: @ContractState, account: ContractAddress ) -> (Array, Array, Option) { @@ -821,67 +869,40 @@ mod OptionRound { return (winning_bids, losing_bids, Option::None(())); } // @dev Look at each bid of the account's bids compared to the clearing bid else { - let nonce = self.account_bid_nonce.read(account); + // @dev Get the account's bid amount + let bidder_nonce = self.account_bid_nonce.read(account); + + // @dev Get the clearing bid let clearing_bid_id: felt252 = self.bids_tree.clearing_bid.read(); let clearing_bid: Bid = self.bids_tree._find(clearing_bid_id); let mut clearing_bid_option: Option = Option::None(()); - let mut i = 0; - while i < nonce { - // @dev Check if this bid is the clearing bid - let bid_id: felt252 = self.create_bid_id(account, i); - let bid: Bid = self.bids_tree._find(bid_id); - if bid_id == clearing_bid_id { - clearing_bid_option = Option::Some(bid); - } // @dev Check if this bid is above or below the clearing bid - else { - if bid > clearing_bid { - winning_bids.append(bid); - } else { - losing_bids.append(bid); + + // @dev Iterate over the account's bids and compare them to the clearing bid + for i in 0 + ..bidder_nonce { + // @dev Get the account's i-th bid + let bid_id = self.create_bid_id(account, i); + let bid = self.bids_tree._find(bid_id); + + // @dev If this bid is the clearing bid it is special because it could be + // mintable & refundable + if bid_id == clearing_bid_id { + clearing_bid_option = Option::Some(bid); + } // @dev If this bid is now the clearing bid, check if this bid is above or below the clearing bid + else { + if bid > clearing_bid { + winning_bids.append(bid); + } else { + losing_bids.append(bid); + } } - } - i += 1; - }; + }; - // @dev Return the winning bids, losing bids, and the clearing bid if the account owns it + // @dev Return the winning bids, losing bids, and the clearing bid if owned (winning_bids, losing_bids, clearing_bid_option) } } - // @dev Calculate the maximum payout for a single option - fn _max_payout_per_option( - self: @ContractState, strike_price: u256, cap_level: u128 - ) -> u256 { - (strike_price * cap_level.into()) / BPS - } - - // @dev Calculate the actual payout for a single option - fn calculate_payout_per_option( - self: @ContractState, strike_price: u256, cap_level: u128, settlement_price: u256 - ) -> u256 { - if (settlement_price <= strike_price) { - 0 - } else { - let uncapped = settlement_price - strike_price; - let capped = self._max_payout_per_option(strike_price, cap_level); - - min(capped, uncapped) - } - } - - // @dev Calculate the total number of options available to sell in the auction - fn calculate_total_options_available( - self: @ContractState, starting_liquidity: u256, strike_price: u256, cap_level: u128 - ) -> u256 { - let capped = self._max_payout_per_option(strike_price, cap_level); - match capped == 0 { - // @dev If the max payout per option is 0, then there are 0 options to sell - true => 0, - // @dev Else the number of options available is the starting liquidity divided by the capped amount - false => starting_liquidity / capped - } - } - // @dev Get a dispatcher for the vault fn get_vault_dispatcher(self: @ContractState) -> IVaultDispatcher { IVaultDispatcher { contract_address: self.vault_address.read() } @@ -893,3 +914,4 @@ mod OptionRound { } } } + diff --git a/src/option_round/interface.cairo b/src/option_round/interface.cairo index 70305232..de94e451 100644 --- a/src/option_round/interface.cairo +++ b/src/option_round/interface.cairo @@ -1,12 +1,32 @@ use starknet::{ContractAddress, StorePacking}; -use openzeppelin::token::erc20::interface::ERC20ABIDispatcher; -use pitch_lake_starknet::{ - option_round::{contract::OptionRound,}, - market_aggregator::interface::{IMarketAggregatorDispatcher, IMarketAggregatorDispatcherTrait}, - types::{OptionRoundState, OptionRoundConstructorParams, Bid,} -}; - -// The option round contract interface +use pitch_lake::types::{Bid}; + +// An enum for each state an option round can be in +#[derive(Default, Copy, Drop, Serde, PartialEq, starknet::Store)] +enum OptionRoundState { + #[default] + 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 +} + +// @dev Data needed for a round's auction to start +#[derive(Default, PartialEq, Copy, Drop, Serde, starknet::Store)] +struct PricingData { + strike_price: u256, + cap_level: u128, + reserve_price: u256, +} + +#[derive(Drop, Serde)] +struct ConstructorArgs { + vault_address: ContractAddress, + round_id: u256, + pricing_data: PricingData +} + +// The interface for an option round contract #[starknet::interface] trait IOptionRound { /// Reads /// @@ -22,6 +42,9 @@ trait IOptionRound { // @dev The state of this round fn get_state(self: @TContractState) -> OptionRoundState; + // @dev Get the round's deployment date + fn get_deployment_date(self: @TContractState) -> u64; + // @dev Get the date the auction can start fn get_auction_start_date(self: @TContractState) -> u64; @@ -31,27 +54,28 @@ trait IOptionRound { // @dev Get the date the round can settle fn get_option_settlement_date(self: @TContractState) -> u64; - // @dev The total ETH locked at the start of the auction - fn get_starting_liquidity(self: @TContractState) -> u256; - - // @dev The total ETH not sold in the auction - fn get_unsold_liquidity(self: @TContractState) -> u256; - // @dev The minimum price per option fn get_reserve_price(self: @TContractState) -> u256; // @dev The strike price for this round in wei fn get_strike_price(self: @TContractState) -> u256; - // @dev The % points (BPS) above the TWAP to cap the payout per option + // @dev The percentage points (BPS) above the TWAP to cap the payout per option + // @note E.g. 3333 tranlates to a capped payout of 33.33% above the settlement price fn get_cap_level(self: @TContractState) -> u128; + // @dev The total ETH locked at the start of the auction + fn get_starting_liquidity(self: @TContractState) -> u256; + // @dev The total number of options available in the auction fn get_options_available(self: @TContractState) -> u256; // @dev The total options sold after in the auction fn get_options_sold(self: @TContractState) -> u256; + // @dev The total ETH not sold in the auction + fn get_unsold_liquidity(self: @TContractState) -> u256; + // @dev The price paid for each option after the auction ends fn get_clearing_price(self: @TContractState) -> u256; @@ -66,10 +90,6 @@ trait IOptionRound { /// Bids - // @dev The number of bids an account has placed - // @param account: The account to get the number of bids for - fn get_account_bid_nonce(self: @TContractState, account: ContractAddress) -> u64; - // @dev The nonce of the entire bid tree fn get_bid_tree_nonce(self: @TContractState) -> u64; @@ -77,11 +97,15 @@ trait IOptionRound { // @param bid_id: The id of the bid fn get_bid_details(self: @TContractState, bid_id: felt252) -> Bid; + /// Accounts + // @dev The bid ids for an account // @param account: The account to get bid ids for fn get_account_bids(self: @TContractState, account: ContractAddress) -> Array; - /// Accounts + // @dev The number of bids an account has placed + // @param account: The account to get the number of bids for + fn get_account_bid_nonce(self: @TContractState, account: ContractAddress) -> u64; // @dev The amount of ETH an account can refund after the auction ends // @param account: The account to get the refundable balance for @@ -105,6 +129,12 @@ trait IOptionRound { /// State transitions + // @dev Set pricing data for round to start + // @note Pricing data is normally set in the constructor, except for the first round. The first + // round will need to either have its pricing data set manually or the vault will need to deploy + // with the data + proofs already computed. + fn set_pricing_data(ref self: TContractState, pricing_data: PricingData); + // @dev Start the round's auction, return the options available in the auction // @param starting_liquidity: The total amount of ETH being locked in the auction fn start_auction(ref self: TContractState, starting_liquidity: u256) -> u256; @@ -115,12 +145,6 @@ trait IOptionRound { // @dev Settle the round, return the total payout for all of the (sold) options fn settle_round(ref self: TContractState, settlement_price: u256) -> u256; - - // @note Probably removing this - fn update_round_params( - ref self: TContractState, reserve_price: u256, cap_level: u128, strike_price: u256 - ); - /// Account functions // @dev The caller places a bid in the auction diff --git a/src/tests.cairo b/src/tests.cairo index 586a1de4..8c2751bb 100644 --- a/src/tests.cairo +++ b/src/tests.cairo @@ -44,6 +44,7 @@ mod option_round { mod option_settle_tests; } mod caller_is_not_vault_tests; + mod fulfill_request_tests; } } @@ -56,12 +57,6 @@ mod deployment { #[cfg(test)] mod misc { //mod eth_test; - mod pitch_lake_test; - mod lp_token { - mod lp_token_tests; - mod deployment_tests; - } - mod unallocated_liquidity_tests; } #[cfg(test)] @@ -69,9 +64,7 @@ mod utils { mod facades { mod vault_facade; mod option_round_facade; - mod lp_token_facade; mod sanity_checks; - mod market_aggregator_facade; } mod helpers { @@ -82,7 +75,6 @@ mod utils { } mod lib { - mod structs; mod test_accounts; mod variables; } diff --git a/src/tests/deployment/constructor_tests.cairo b/src/tests/deployment/constructor_tests.cairo index 5e828712..74aa1770 100644 --- a/src/tests/deployment/constructor_tests.cairo +++ b/src/tests/deployment/constructor_tests.cairo @@ -1,11 +1,7 @@ -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; -use starknet::{ - ClassHash, ContractAddress, contract_address_const, deploy_syscall, SyscallResult, - Felt252TryIntoContractAddress, get_contract_address, get_block_timestamp, - testing::{set_block_timestamp, set_contract_address} -}; -use pitch_lake_starknet::{ - library::eth::Eth, types::{OptionRoundState, OptionRoundConstructorParams}, +//use openzeppelin_token::erc20::interface::{ERC20ABIDispatcherTrait,}; +use starknet::ContractAddress; +use pitch_lake::{ + library::eth::Eth, vault::{ contract::Vault, interface::{ @@ -13,13 +9,8 @@ use pitch_lake_starknet::{ } }, option_round::{ - contract::{OptionRound,}, interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,}, - }, - market_aggregator::{ - interface::{ - IMarketAggregator, IMarketAggregatorDispatcher, IMarketAggregatorDispatcherTrait, - }, - types::Errors + //contract::{OptionRound,}, + interface::{OptionRoundState, IOptionRoundDispatcher, IOptionRoundDispatcherTrait,}, }, tests::{ utils::{ @@ -32,14 +23,11 @@ use pitch_lake_starknet::{ facades::{ vault_facade::VaultFacadeTrait, option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait}, - market_aggregator_facade::{MarketAggregatorFacadeTrait} }, - lib::{ - variables::{decimals}, - test_accounts::{ - liquidity_provider_1, liquidity_provider_2, option_bidder_buyer_1, - option_bidder_buyer_2 - }, + lib::{variables::{decimals}, // test_accounts::{ + // liquidity_provider_1, liquidity_provider_2, option_bidder_buyer_1, + // option_bidder_buyer_2 + // }, } }, }, @@ -48,10 +36,7 @@ use debug::PrintTrait; /// Constructor Tests /// -// These tests deal with the lifecycle of an option round, from deployment to settlement -// Test the vault deploys with current round 0 (settled), next round 1 (open), -// vault manager is set #[test] #[available_gas(50000000)] fn test_vault_constructor() { @@ -59,11 +44,6 @@ fn test_vault_constructor() { let mut current_round = vault.get_current_round(); let current_round_id = vault.get_current_round_id(); - // Constructor args - assert_eq!(vault.get_round_transition_period(), 1000); - assert_eq!(vault.get_auction_run_time(), 1000); - assert_eq!(vault.get_option_run_time(), 1000); - // Check current round is 1 assert(current_round_id == 1, 'current round should be 1'); // Check current round is open and next round is settled @@ -108,21 +88,6 @@ fn test_option_round_constructor() { assert_eq!(current_round.get_option_settlement_date(), option_settlement_date); assert!(current_round.get_state() == OptionRoundState::Open, "state does not match"); -// Test reserve price, cap level, strike price + // Test reserve price, cap level, strike price // - might need to deploy a custom option round for this } - - -// Test market aggregator is deployed -// @note Need make sure mock has both setter & getter implementations -#[test] -#[available_gas(50000000)] -fn test_market_aggregator_deployed() { - let (mut vault_facade, _) = setup_facade(); - let mk_agg = vault_facade.get_market_aggregator_facade(); - - assert(mk_agg.contract_address.is_non_zero(), 'mk agg addr shd be set'); - // Entry point will fail if contract not deployed - assert_eq!(mk_agg.get_TWAP_for_time_period(1, 1).is_none(), true); -} - diff --git a/src/tests/deployment/initializing_option_round_params_tests.cairo b/src/tests/deployment/initializing_option_round_params_tests.cairo index cfae7dc2..a0f30f44 100644 --- a/src/tests/deployment/initializing_option_round_params_tests.cairo +++ b/src/tests/deployment/initializing_option_round_params_tests.cairo @@ -1,30 +1,18 @@ -use pitch_lake_starknet::tests::utils::facades::option_round_facade::OptionRoundFacadeTrait; use starknet::{ ClassHash, ContractAddress, contract_address_const, deploy_syscall, Felt252TryIntoContractAddress, get_contract_address, get_block_timestamp, testing::{set_block_timestamp, set_contract_address}, contract_address::ContractAddressZeroable }; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; +use openzeppelin_token::erc20::interface::ERC20ABIDispatcherTrait; -use pitch_lake_starknet::{ - library::eth::Eth, - market_aggregator::interface::{ - IMarketAggregatorMockDispatcher, IMarketAggregatorMockDispatcherTrait - }, - vault::{contract::Vault, interface::{IVaultDispatcher, IVaultDispatcherTrait},}, +use pitch_lake::{ + library::eth::Eth, types::Consts::BPS, + vault::{contract::Vault, interface::{VaultType, IVaultDispatcher, IVaultDispatcherTrait},}, option_round::interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait}, - contracts::{ - pitch_lake::{ - IPitchLakeDispatcher, IPitchLakeSafeDispatcher, IPitchLakeDispatcherTrait, PitchLake, - IPitchLakeSafeDispatcherTrait - }, - }, tests::{ utils::{ helpers::{ - setup::{ - decimals, setup_facade, setup_facade_vault_type, deploy_vault, deploy_pitch_lake - }, + setup::{deploy_eth, decimals, setup_facade, setup_facade_vault_type, deploy_vault,}, event_helpers::{pop_log, assert_no_events_left}, accelerators::{ accelerate_to_auctioning, accelerate_to_running, accelerate_to_settled @@ -35,13 +23,14 @@ use pitch_lake_starknet::{ option_bidder_buyer_2, option_bidder_buyer_3, option_bidder_buyer_4, bystander, }, facades::{ - option_round_facade::{OptionRoundFacade, OptionRoundFacadeImpl}, + option_round_facade::{ + OptionRoundFacade, OptionRoundFacadeTrait, OptionRoundFacadeImpl + }, vault_facade::{VaultFacade, VaultFacadeTrait}, - market_aggregator_facade::{MarketAggregatorFacade, MarketAggregatorFacadeTrait} }, }, }, - types::VaultType, library::utils::{calculate_strike_price} + library::pricing_utils::calculate_strike_price }; use debug::PrintTrait; @@ -73,16 +62,16 @@ fn test_calculated_strike_price() { #[available_gas(50000000)] fn test_calculated_strike_price_2() { let avg_basefee = to_gwei(100); - let volatility = 10000; // 100.33% + let volatility = 10033; // 100.33% let strike_itm = calculate_strike_price(VaultType::InTheMoney, avg_basefee, volatility); let strike_atm = calculate_strike_price(VaultType::AtTheMoney, avg_basefee, volatility); let strike_otm = calculate_strike_price(VaultType::OutOfMoney, avg_basefee, volatility); assert_eq!(strike_atm, avg_basefee); - assert_eq!(strike_otm, 2 * avg_basefee); + assert_eq!(strike_itm, avg_basefee / 2); + assert_eq!(strike_otm, (BPS.into() + volatility.into()) * avg_basefee / BPS.into()); // @note Return to this test when 0 strike is discussed - assert_eq!(strike_itm, avg_basefee); } // @note Return to this test when 0 strike is discussed @@ -93,40 +82,26 @@ fn test_calculated_strike_ITM_high_vol() { let volatility = 20000; // 200.00% let strike_itm = calculate_strike_price(VaultType::InTheMoney, avg_basefee, volatility); - assert_eq!(strike_itm, avg_basefee); + assert_eq!(strike_itm, avg_basefee / 2); } #[test] #[available_gas(200000000)] fn test_strike_prices_across_rounds_ATM() { - let (mut vault, _) = setup_facade_vault_type(VaultType::AtTheMoney); - let mut round1 = vault.get_current_round(); - - let k1 = round1.get_strike_price(); - accelerate_to_auctioning(ref vault); - accelerate_to_running(ref vault); - let s1 = to_gwei(20); - accelerate_to_settled(ref vault, s1); -// let mut round2 = vault.get_current_round(); -// let k2 = round2.get_strike_price(); -// accelerate_to_auctioning(ref vault); -// accelerate_to_running(ref vault); -// let s2 = to_gwei(40); -// accelerate_to_settled(ref vault, s2); - -// let mut round3 = vault.get_current_round(); -// let k3 = round3.get_strike_price(); -// println!("k1: {}, k2: {}, k3: {}", k1, k2, k3); - -// assert_eq!(k2, s1); -// assert_eq!(k3, s2); -} - -#[test] -#[available_gas(200000000)] -fn test_strike_prices_across_rounds_ITM() { - let (vault, mk_agg) = setup_facade_vault_type(VaultType::InTheMoney); + let mut vault = setup_facade_vault_type(VaultType::AtTheMoney); + for i in 0_u256 + ..3 { + accelerate_to_auctioning(ref vault); + accelerate_to_running(ref vault); + + let k_0 = to_gwei(i + 100); + accelerate_to_settled(ref vault, k_0); + let mut current_round = vault.get_current_round(); + let k_1 = current_round.get_strike_price(); + + assert_eq!(k_0, k_1); + }; } @@ -135,43 +110,39 @@ fn test_strike_prices_across_rounds_ITM() { #[available_gas(50000000)] #[ignore] fn test_strike_price_based_on_vault_types() { - // Deploy pitch lake - let pitch_lake_dispatcher: IPitchLakeDispatcher = deploy_pitch_lake(); - // Fetch vaults as facades - let mut vault_dispatcher_at_the_money = VaultFacade { - vault_dispatcher: pitch_lake_dispatcher.at_the_money_vault() - }; - let mut vault_dispatcher_in_the_money = VaultFacade { - vault_dispatcher: pitch_lake_dispatcher.in_the_money_vault() - }; - let mut vault_dispatcher_out_the_money = VaultFacade { - vault_dispatcher: pitch_lake_dispatcher.out_the_money_vault() - }; + // Deploy different vault types + let mut vault_atm = setup_facade_vault_type(VaultType::AtTheMoney); + let mut _vault_itm = setup_facade_vault_type(VaultType::InTheMoney); + let mut _vault_otm = setup_facade_vault_type(VaultType::OutOfMoney); // LP deposits into each round 1 because a round cannot start auctioning without liquidity - let deposit_amount_wei: u256 = 100 * decimals(); - vault_dispatcher_at_the_money.deposit(deposit_amount_wei, liquidity_provider_1()); - vault_dispatcher_in_the_money.deposit(deposit_amount_wei, liquidity_provider_1()); - vault_dispatcher_out_the_money.deposit(deposit_amount_wei, liquidity_provider_1()); + let deposit = to_gwei(1000); + vault_atm.deposit(deposit, liquidity_provider_1()); + _vault_itm.deposit(deposit, liquidity_provider_1()); + _vault_otm.deposit(deposit, liquidity_provider_1()); + + // Start the rounds + let settlement_price = to_gwei(100); + accelerate_to_settled(ref vault_atm, settlement_price); + accelerate_to_settled(ref _vault_itm, settlement_price); + accelerate_to_settled(ref _vault_otm, settlement_price); // Get each round 1 dispatcher - let mut atm = vault_dispatcher_at_the_money.get_current_round(); - let mut itm = vault_dispatcher_in_the_money.get_current_round(); - let mut otm = vault_dispatcher_out_the_money.get_current_round(); + let mut atm_round = vault_atm.get_current_round(); + let mut _itm_round = _vault_itm.get_current_round(); + let mut _otm_round = _vault_otm.get_current_round(); // Check the strike price of each vault's round 1 - let atm_strike_price = atm.get_strike_price(); - let itm_strike_price = itm.get_strike_price(); - let otm_strike_price = otm.get_strike_price(); -//let atm_avg_basefee = atm.get_current_average_basefee(); -//let itm_avg_basefee = itm.get_current_average_basefee(); -//let otm_avg_basefee = otm.get_current_average_basefee(); - -//assert(atm_strike_price == atm_avg_basefee, 'ATM stike wrong'); -//assert(itm_strike_price > itm_avg_basefee, 'ITM stike wrong'); + let atm_strike_price = atm_round.get_strike_price(); + let _itm_strike_price = _itm_round.get_strike_price(); + let _otm_strike_price = _otm_round.get_strike_price(); + + assert(settlement_price == atm_strike_price, 'ATM stike wrong'); + //assert(itm_strike_price > itm_avg_basefee, 'ITM stike wrong'); //assert(otm_strike_price < otm_avg_basefee, 'OTM stike wrong'); } // @note Add tests for other init params. Reserve price, cap levels etc. -// @note Add test that option round params are logical (auction start time < auction end time < option settlement time) +// @note Add test that option round params are logical (auction start time < auction end time < +// option settlement time) diff --git a/src/tests/misc/eth_test.cairo b/src/tests/misc/eth_test.cairo index 6c4cd177..09e9710c 100644 --- a/src/tests/misc/eth_test.cairo +++ b/src/tests/misc/eth_test.cairo @@ -1,173 +1,173 @@ -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; -use starknet::{ - ClassHash, ContractAddress, contract_address_const, deploy_syscall, - Felt252TryIntoContractAddress, get_contract_address, -}; -use debug::PrintTrait; - -use pitch_lake_starknet::eth::Eth; -const NAME: felt252 = 111; -const SYMBOL: felt252 = 222; -const DECIMALS: u8 = 18_u8; -const SUPPLY: u256 = 2000; -const VALUE: u256 = 300; - - -fn OWNER() -> ContractAddress { - contract_address_const::<10>() -} -fn SPENDER() -> ContractAddress { - contract_address_const::<20>() -} -fn RECIPIENT() -> ContractAddress { - contract_address_const::<30>() -} -fn OPERATOR() -> ContractAddress { - contract_address_const::<40>() -} - -fn deploy() -> ERC20SafeDispatcher { - let mut calldata = array![]; - - calldata.append_serde(NAME); - calldata.append_serde(SYMBOL); - calldata.append_serde(SUPPLY); - calldata.append_serde(OWNER()); - - let (address, _) = deploy_syscall( - Eth::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), true - ) - .expect('DEPLOY_AD_FAILED'); - return ERC20ABIDispatcher { contract_address: address }; -} - -#[test] -#[available_gas(50000000)] -fn test_name() { - let safe_dispatcher = deploy(); - let name: felt252 = safe_dispatcher.name().unwrap(); - assert(name == NAME, 'invalid name'); -} - -#[test] -#[available_gas(50000000)] -fn test_symbol() { - let safe_dispatcher = deploy(); - let symbol: felt252 = safe_dispatcher.symbol().unwrap(); - assert(symbol == SYMBOL, 'invalid symbol'); -} - -#[test] -#[available_gas(50000000)] -fn test_decimals() { - let safe_dispatcher = deploy(); - let decimals: u8 = safe_dispatcher.decimals().unwrap(); - assert(decimals == 18, 'invalid decimals'); -} - -#[test] -#[available_gas(50000000)] -fn test_balanceOf() { - let safe_dispatcher = deploy(); - let account: ContractAddress = ContractAddressZeroable::zero(); - let balance: u256 = safe_dispatcher.balance_of(account).unwrap(); - assert(balance == 0, 'invalid balance'); -} - -#[test] -#[available_gas(50000000)] -fn test_allowance() { - let safe_dispatcher = deploy(); - let owner: ContractAddress = ContractAddressZeroable::zero(); - let spender: ContractAddress = ContractAddressZeroable::zero(); - let allowance: u256 = safe_dispatcher.allowance(owner, spender).unwrap(); - assert(allowance == 0, 'invalid allowance'); -} - -#[test] -#[available_gas(50000000)] -fn test_transfer_zero() { - let safe_dispatcher = deploy(); - let recipient: ContractAddress = ContractAddressZeroable::zero(); - let amount: u256 = 0; - let result: bool = safe_dispatcher.transfer(RECIPIENT(), amount).unwrap(); - assert(result == true, 'transfer failed'); -} - -#[test] -#[available_gas(50000000)] -fn test_transfer_insufficient_balance() { - let safe_dispatcher = deploy(); - let recipient: ContractAddress = ContractAddressZeroable::zero(); - let amount: u256 = 1; - let result: starknet::SyscallResult = safe_dispatcher.transfer(recipient, amount); - // result.unwrap() - let result: bool = result.unwrap(); -} - -#[test] -#[available_gas(50000000)] -fn test_transfer_from_zero() { - let safe_dispatcher = deploy(); - let owner: ContractAddress = ContractAddressZeroable::zero(); - let spender: ContractAddress = ContractAddressZeroable::zero(); - let amount: u256 = 0; - let result: bool = safe_dispatcher.transfer_from(owner, spender, amount).unwrap(); - assert(result == true, 'transfer from failed'); -} - -#[test] -#[available_gas(50000000)] -fn test_transfer_from_insufficient_allowance() { - let safe_dispatcher = deploy(); - let owner: ContractAddress = ContractAddressZeroable::zero(); - let spender: ContractAddress = ContractAddressZeroable::zero(); - let amount: u256 = 1; - let result: bool = safe_dispatcher.transfer_from(owner, spender, amount).unwrap(); - assert(result == false, 'should have failed'); -} - -#[test] -#[available_gas(50000000)] -fn test_transfer_from_insufficient_balance() { - let safe_dispatcher = deploy(); - let owner: ContractAddress = ContractAddressZeroable::zero(); - let spender: ContractAddress = ContractAddressZeroable::zero(); - let amount: u256 = 1; - let result: bool = safe_dispatcher.transfer_from(owner, spender, amount).unwrap(); - assert(result == false, 'should have failed'); -} - -#[test] -#[available_gas(50000000)] -fn test_approve() { - let safe_dispatcher = deploy(); - let spender: ContractAddress = ContractAddressZeroable::zero(); - let amount: u256 = 1; - let result: bool = safe_dispatcher.approve(spender, amount).unwrap(); - assert(result == true, 'approve failed'); -} - -#[test] -#[available_gas(50000000)] -fn test_approve_update() { - let safe_dispatcher = deploy(); - let spender: ContractAddress = ContractAddressZeroable::zero(); - - let caller_address = contract_address_const::<1>(); - let caller_account: ContractAddress = caller_address; - starknet::testing::set_contract_address(caller_address); - - let mut allowance_amount = safe_dispatcher.allowance(caller_account, spender).unwrap(); - assert(allowance_amount == 0, 'invalid allowance 0'); - let amount1: u256 = 1; - let result1: bool = safe_dispatcher.approve(spender, amount1).unwrap(); - assert(result1 == true, 'approve 1 failed'); - allowance_amount = safe_dispatcher.allowance(caller_account, spender).unwrap(); - assert(allowance_amount == 1, 'invalid allowance 1'); - let amount2: u256 = 2; - let result2: bool = safe_dispatcher.approve(spender, amount2).unwrap(); - assert(result2 == true, 'approve 2 failed'); - allowance_amount = safe_dispatcher.allowance(caller_account, spender).unwrap(); - assert(allowance_amount == 2, 'invalid allowance 2'); -} +//use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; +//use starknet::{ +// ClassHash, ContractAddress, contract_address_const, deploy_syscall, +// Felt252TryIntoContractAddress, get_contract_address, +//}; +//use debug::PrintTrait; +// +//use pitch_lake_starknet::eth::Eth; +//const NAME: felt252 = 111; +//const SYMBOL: felt252 = 222; +//const DECIMALS: u8 = 18_u8; +//const SUPPLY: u256 = 2000; +//const VALUE: u256 = 300; +// +// +//fn OWNER() -> ContractAddress { +// contract_address_const::<10>() +//} +//fn SPENDER() -> ContractAddress { +// contract_address_const::<20>() +//} +//fn RECIPIENT() -> ContractAddress { +// contract_address_const::<30>() +//} +//fn OPERATOR() -> ContractAddress { +// contract_address_const::<40>() +//} +// +//fn deploy() -> ERC20SafeDispatcher { +// let mut calldata = array![]; +// +// calldata.append_serde(NAME); +// calldata.append_serde(SYMBOL); +// calldata.append_serde(SUPPLY); +// calldata.append_serde(OWNER()); +// +// let (address, _) = deploy_syscall( +// Eth::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), true +// ) +// .expect('DEPLOY_AD_FAILED'); +// return ERC20ABIDispatcher { contract_address: address }; +//} +// +//#[test] +//#[available_gas(50000000)] +//fn test_name() { +// let safe_dispatcher = deploy(); +// let name: felt252 = safe_dispatcher.name().unwrap(); +// assert(name == NAME, 'invalid name'); +//} +// +//#[test] +//#[available_gas(50000000)] +//fn test_symbol() { +// let safe_dispatcher = deploy(); +// let symbol: felt252 = safe_dispatcher.symbol().unwrap(); +// assert(symbol == SYMBOL, 'invalid symbol'); +//} +// +//#[test] +//#[available_gas(50000000)] +//fn test_decimals() { +// let safe_dispatcher = deploy(); +// let decimals: u8 = safe_dispatcher.decimals().unwrap(); +// assert(decimals == 18, 'invalid decimals'); +//} +// +//#[test] +//#[available_gas(50000000)] +//fn test_balanceOf() { +// let safe_dispatcher = deploy(); +// let account: ContractAddress = ContractAddressZeroable::zero(); +// let balance: u256 = safe_dispatcher.balance_of(account).unwrap(); +// assert(balance == 0, 'invalid balance'); +//} +// +//#[test] +//#[available_gas(50000000)] +//fn test_allowance() { +// let safe_dispatcher = deploy(); +// let owner: ContractAddress = ContractAddressZeroable::zero(); +// let spender: ContractAddress = ContractAddressZeroable::zero(); +// let allowance: u256 = safe_dispatcher.allowance(owner, spender).unwrap(); +// assert(allowance == 0, 'invalid allowance'); +//} +// +//#[test] +//#[available_gas(50000000)] +//fn test_transfer_zero() { +// let safe_dispatcher = deploy(); +// let recipient: ContractAddress = ContractAddressZeroable::zero(); +// let amount: u256 = 0; +// let result: bool = safe_dispatcher.transfer(RECIPIENT(), amount).unwrap(); +// assert(result == true, 'transfer failed'); +//} +// +//#[test] +//#[available_gas(50000000)] +//fn test_transfer_insufficient_balance() { +// let safe_dispatcher = deploy(); +// let recipient: ContractAddress = ContractAddressZeroable::zero(); +// let amount: u256 = 1; +// let result: starknet::SyscallResult = safe_dispatcher.transfer(recipient, amount); +// // result.unwrap() +// let result: bool = result.unwrap(); +//} +// +//#[test] +//#[available_gas(50000000)] +//fn test_transfer_from_zero() { +// let safe_dispatcher = deploy(); +// let owner: ContractAddress = ContractAddressZeroable::zero(); +// let spender: ContractAddress = ContractAddressZeroable::zero(); +// let amount: u256 = 0; +// let result: bool = safe_dispatcher.transfer_from(owner, spender, amount).unwrap(); +// assert(result == true, 'transfer from failed'); +//} +// +//#[test] +//#[available_gas(50000000)] +//fn test_transfer_from_insufficient_allowance() { +// let safe_dispatcher = deploy(); +// let owner: ContractAddress = ContractAddressZeroable::zero(); +// let spender: ContractAddress = ContractAddressZeroable::zero(); +// let amount: u256 = 1; +// let result: bool = safe_dispatcher.transfer_from(owner, spender, amount).unwrap(); +// assert(result == false, 'should have failed'); +//} +// +//#[test] +//#[available_gas(50000000)] +//fn test_transfer_from_insufficient_balance() { +// let safe_dispatcher = deploy(); +// let owner: ContractAddress = ContractAddressZeroable::zero(); +// let spender: ContractAddress = ContractAddressZeroable::zero(); +// let amount: u256 = 1; +// let result: bool = safe_dispatcher.transfer_from(owner, spender, amount).unwrap(); +// assert(result == false, 'should have failed'); +//} +// +//#[test] +//#[available_gas(50000000)] +//fn test_approve() { +// let safe_dispatcher = deploy(); +// let spender: ContractAddress = ContractAddressZeroable::zero(); +// let amount: u256 = 1; +// let result: bool = safe_dispatcher.approve(spender, amount).unwrap(); +// assert(result == true, 'approve failed'); +//} +// +//#[test] +//#[available_gas(50000000)] +//fn test_approve_update() { +// let safe_dispatcher = deploy(); +// let spender: ContractAddress = ContractAddressZeroable::zero(); +// +// let caller_address = contract_address_const::<1>(); +// let caller_account: ContractAddress = caller_address; +// starknet::testing::set_contract_address(caller_address); +// +// let mut allowance_amount = safe_dispatcher.allowance(caller_account, spender).unwrap(); +// assert(allowance_amount == 0, 'invalid allowance 0'); +// let amount1: u256 = 1; +// let result1: bool = safe_dispatcher.approve(spender, amount1).unwrap(); +// assert(result1 == true, 'approve 1 failed'); +// allowance_amount = safe_dispatcher.allowance(caller_account, spender).unwrap(); +// assert(allowance_amount == 1, 'invalid allowance 1'); +// let amount2: u256 = 2; +// let result2: bool = safe_dispatcher.approve(spender, amount2).unwrap(); +// assert(result2 == true, 'approve 2 failed'); +// allowance_amount = safe_dispatcher.allowance(caller_account, spender).unwrap(); +// assert(allowance_amount == 2, 'invalid allowance 2'); +//} diff --git a/src/tests/misc/lp_token/deployment_tests.cairo b/src/tests/misc/lp_token/deployment_tests.cairo deleted file mode 100644 index 3bc6652f..00000000 --- a/src/tests/misc/lp_token/deployment_tests.cairo +++ /dev/null @@ -1,8 +0,0 @@ -// @note Add test for lp token constructor -// - token should know the vault & option round addresses (mk agg too ?) -// - token should know what round it comes from -// - should discuess name/symbol for each token, PLPT1, Pitchlake LP Token 1 ? (low priority) - -// @note Add test that only the vault can mint/burn tokens - - diff --git a/src/tests/misc/lp_token/lp_token_tests.cairo b/src/tests/misc/lp_token/lp_token_tests.cairo deleted file mode 100644 index 98ca5040..00000000 --- a/src/tests/misc/lp_token/lp_token_tests.cairo +++ /dev/null @@ -1,402 +0,0 @@ -//use starknet::{ -// ClassHash, ContractAddress, contract_address_const, deploy_syscall, -// Felt252TryIntoContractAddress, get_contract_address, get_block_timestamp, -// testing::{set_block_timestamp, set_contract_address} -//}; -//use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; -//use pitch_lake_starknet::{ -// library::eth::Eth, -// tests::{ -// utils::{ -// helpers::{ -// accelerators::{timeskip_and_settle_round}, -// setup::{setup_facade, decimals, deploy_vault}, -// event_helpers::{pop_log, assert_no_events_left, assert_event_transfer} -// }, -// lib::test_accounts::{ -// liquidity_provider_1, liquidity_provider_2, option_bidder_buyer_1, -// option_bidder_buyer_2, option_bidder_buyer_3, option_bidder_buyer_4, -// }, -// facades::{ -// vault_facade::{VaultFacade, VaultFacadeTrait}, -// option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait}, -// }, -// }, -// } -//}; -// -///// -///// Position -> LP Tokens /// -///// -// -//// Test converting position->lp tokens fails if the auction has not ended -//#[ignore] -//#[test] -//#[available_gas(50000000)] -//#[should_panic(expected: ('Cannot tokenize until auction ends', 'ENTRYPOINT_FAILED',))] -//fn test_convert_position_to_lp_tokens_while_auctioning_failure() { -// let (mut vault_facade, _) = setup_facade(); -// // Deposit liquidity so auction can start -// let deposit_amount_wei = 50 * decimals(); -// vault_facade.deposit(deposit_amount_wei, liquidity_provider_1()); -// // Start the auction -// vault_facade.start_auction(); -// // Try to convert position to tokens, should fail since premiums are not known yet -// vault_facade.convert_position_to_lp_tokens(1, liquidity_provider_1()); -//} -// -//// Test converting position -> lp tokens while the current round is settled -//// @note We should discuss if there is a use case for this. I do not think it breaks any logic -//// (it should act the same as if the round were Running), but the liquidity is all unlocked in the -//// next round during this time and could just be withdrawn instead of tokenized. -//// @note If we allow this, the premiums still need to be collected, since they are already sitting in the -//// next round, we can mark the current round's premiums collected, and leave the amount sitting in the next round -//#[ignore] -//#[test] -//#[available_gas(50000000)] -//#[should_panic(expected: ('Cannot tokenize when round is settled?', 'ENTRYPOINT_FAILED',))] -//fn test_convert_position_to_lp_tokens_while_settled__TODO__() { -// let (mut vault_facade, _) = setup_facade(); -// // LP deposits (into round 1) -// let deposit_amount_wei: u256 = 10000 * decimals(); -// vault_facade.deposit(deposit_amount_wei, liquidity_provider_1()); -// // Start auction -// vault_facade.start_auction(); -// let mut current_round: OptionRoundFacade = vault_facade.get_current_round(); -// // Make bid -// let bid_amount: u256 = current_round.get_total_options_available(); -// let bid_price: u256 = current_round.get_reserve_price(); -// current_round.place_bid(bid_amount, bid_price, option_bidder_buyer_1()); -// // Settle auction -// set_block_timestamp(current_round.get_auction_end_date() + 1); -// let (clearing_price, _) = vault_facade.end_auction(); -// assert(clearing_price == bid_price, 'clearing price wrong'); -// // Settle option round -// set_block_timestamp(current_round.get_option_settlement_date() + 1); -// vault_facade.settle_option_round(); -// // Convert position -> tokens while current round is Settled -// vault_facade.convert_position_to_lp_tokens(1, liquidity_provider_1()); -//// @note verify expected behavior -//} -// -//// Test that converting position -> LP tokens auto-collects premiums and updates the position -//// @dev Check unallocated assertions after speaking with Dhruv, should unallocated_balance be premiums + next_round_deposit, or just next_round_deposit ? -//// @dev Is this test suffice for knowing withdrawCheckpoint and current_roundPosition is updated correctly? If lp_collateral is correct then withdrawCheckpoint must be right ? -//#[ignore] -//#[test] -//#[available_gas(50000000)] -//fn test_convert_position_to_lp_tokens_success() { // -// let (mut vault_facade, _) = setup_facade(); -// // LPs deposit 50/50 into the next round (round 1) -// let deposit_amount_wei: u256 = 10000 * decimals(); -// vault_facade.deposit(deposit_amount_wei, liquidity_provider_1()); -// vault_facade.deposit(deposit_amount_wei, liquidity_provider_2()); -// // Start auction -// let total_options_available = vault_facade.start_auction(); -// let mut current_round: OptionRoundFacade = vault_facade.get_current_round(); -// // Make bid -// -// let reserve_price = current_round.get_reserve_price(); -// let auction_end_time = current_round.get_auction_end_date(); -// -// let bid_amount: u256 = total_options_available; -// let bid_price: u256 = reserve_price; -// current_round.place_bid(bid_amount, bid_price, option_bidder_buyer_1()); -// // Settle auction -// set_block_timestamp(auction_end_time + 1); -// // Get initial states before conversion -// //let lp1_premiums_init = vault_facade.get_premiums_for(liquidity_provider_1(), 'todo'.into()); -// //let lp2_premiums_init = vault_facade.get_premiums_for(liquidity_provider_2(), 'todo'.into()); -// let (lp1_collateral_init, _lp1_unallocated_init) = vault_facade -// .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); -// let (lp2_collateral_init, lp2_unallocated_init) = vault_facade -// .get_lp_locked_and_unlocked_balance(liquidity_provider_2()); -// // let (current_round_collateral_init, _current_round_unallocated_init) = current_round -// // .get_all_round_liquidity(); -// // let (next_round_collateral_init, next_round_unallocated_init) = next_round -// // .get_all_round_liquidity(); -// // LP1 converts 1/2 of their position to tokens -// let tokenizing_amount = deposit_amount_wei / 4; -// vault_facade.convert_position_to_lp_tokens(tokenizing_amount, liquidity_provider_1()); -// // Get states after conversion -// //let lp1_premiums_final = vault_facade.get_premiums_for(liquidity_provider_1(), 'todo'.into()); -// //let lp2_premiums_final = vault_facade.get_premiums_for(liquidity_provider_2(), 'todo'.into()); -// let (lp1_collateral_final, lp1_unallocated_final) = vault_facade -// .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); -// let (lp2_collateral_final, lp2_unallocated_final) = vault_facade -// .get_lp_locked_and_unlocked_balance(liquidity_provider_2()); -// // let (current_round_collateral_final, current_round_unallocated_final) = current_round -// // .get_all_round_liquidity(); -// // let (next_round_collateral_final, next_round_unallocated_final) = next_round -// // .get_all_round_liquidity(); -// // Assert all premiums were collected (deposit into the next round) -// let expected_premiums_share = current_round.total_premiums() / 2; -// //assert( -// // lp1_premiums_final == lp1_premiums_init -// // - expected_premiums_share && lp1_premiums_final == 0, -// // 'lp1 premiums incorrect' -// //); // @dev need both checks ? -// //assert(lp2_premiums_final == lp2_premiums_init, 'lp2 premiums shd not change'); -// // @dev Some of LP1's collateral is now represented as tokens, this means their collateral will decrease, -// // but the round's will remain the same. -// assert( -// lp1_collateral_final == lp1_collateral_init - tokenizing_amount, 'premiums not collected' -// ); -// assert(lp2_collateral_final == lp2_collateral_init, 'premiums shd not collect'); -// // assert( -// // current_round_collateral_final == current_round_collateral_init, -// // 'round collateral shd not change' -// // ); -// // assert(next_round_collateral_final == next_round_collateral_init, 'round not locked yet'); -// // @dev Collected premium should be deposited into the next round -// // @dev Find out if unallocated is premiums + next round depoist or just next round deposit -// assert(lp1_unallocated_final == 'TODO'.into(), 'lp1 unallocated shd ...'); -// assert(lp2_unallocated_final == lp2_unallocated_init, 'lp2 shd not change'); -//// assert(current_round_unallocated_final == 'TODO'.into(), 'round unallocated shd ...'); -//// assert( -//// next_round_unallocated_final == next_round_unallocated_init + expected_premiums_share, -//// 'premiums not deposited' -//// ); -//// Check ETH transferred from current -> next round -//// assert_event_transfer( -//// eth.contract_address, -//// current_round.contract_address(), -//// next_round.contract_address(), -//// expected_premiums_share -//// ); -//// @note Add lp token transfer event assert function -//// assert_lp_event_transfer(lp_token_contract, from: 0, to: LP1, amount: tokenizing_amount) -//} -// -//// @note Add test that the auto-collect does nothing if LP has already collected their premiums -//// @note Add test that tokenizing > collateral fails -// -///// -///// LP Tokens -> Position /// -///// -// -//// Test converting tokens-> position deposits into the current round -//// @dev If user can choose target round when converting, then target must be > withdrawCheckpoint, -//// and the round target-1 must be settled. -//#[ignore] -//#[test] -//#[available_gas(50000000)] -//fn test_convert_lp_tokens_to_position_is_always_deposit_into_current_round() { // -// let (mut vault_facade, _) = setup_facade(); -// // LP deposits (into round 1) -// let deposit_amount_wei: u256 = 10000 * decimals(); -// vault_facade.deposit(deposit_amount_wei, liquidity_provider_1()); -// // Start auction -// vault_facade.start_auction(); -// let mut current_round: OptionRoundFacade = vault_facade.get_current_round(); -// -// let reserve_price = current_round.get_reserve_price(); -// let total_options_available = current_round.get_total_options_available(); -// let auction_end_time = current_round.get_auction_end_date(); -// -// // Make bid -// let bid_amount: u256 = total_options_available; -// let bid_price: u256 = reserve_price; -// current_round.place_bid(bid_amount, bid_price, option_bidder_buyer_1()); -// // Settle auction -// set_block_timestamp(auction_end_time + 1); -// vault_facade.end_auction(); -// -// // Convert position -> tokens (while current is Running) -// vault_facade.convert_position_to_lp_tokens(deposit_amount_wei, liquidity_provider_1()); -// // Initial state -// let (lp_collateral_init, lp_unallocated_init) = vault_facade -// .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); -// // let (current_round_collateral_init, current_round_unallocated_init) = current_round -// // .get_all_round_liquidity(); -// // let (next_round_collateral_init, next_round_unallocated_init) = next_round_facade -// // .get_all_round_liquidity(); -// // -// // Convert some tokens to a position while current is Running -// vault_facade.convert_lp_tokens_to_position(1, deposit_amount_wei / 4, liquidity_provider_1()); -// // Get states after conversion1 -// let (lp_collateral1, lp_unallocated1) = vault_facade -// .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); -// // let (current_round_collateral1, current_round_unallocated1) = current_round -// // .get_all_round_liquidity(); -// // let (next_round_collateral1, next_round_unallocated1) = next_round_facade -// // .get_all_round_liquidity(); -// // -// // Settle option round -// // @dev Do we need to mock the mkagg to say there is no payout for these tests ? -// timeskip_and_settle_round(ref vault_facade); -// -// // Convert some tokens to a position while current is Settled -// vault_facade.convert_lp_tokens_to_position(1, deposit_amount_wei / 4, liquidity_provider_1()); -// // Get states after conversion2 -// let (lp_collateral2, lp_unallocated2) = vault_facade -// .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); -// // let (current_round_collateral2, current_round_unallocated2) = current_round -// // .get_all_round_liquidity(); -// // let (next_round_collateral2, next_round_unallocated2) = next_round_facade -// // .get_all_round_liquidity(); -// // -// /// Start next round's auction -// // @dev Need to jump to time += RTP -// vault_facade.start_auction(); -// let mut next_next_round = vault_facade.get_current_round(); -// -// // Convert some tokens to a position while current is Auctioning -// vault_facade.convert_lp_tokens_to_position(1, deposit_amount_wei / 4, liquidity_provider_1()); -// // Get states after conversion3 -// let (lp_collateral3, lp_unallocated3) = vault_facade -// .get_lp_locked_and_unlocked_balance(liquidity_provider_1()); -// // let (current_round_collateral3, current_round_unallocated3) = current_round -// // .get_all_round_liquidity(); -// // let (next_round_collateral3, next_round_unallocated3) = next_round_facade -// // .get_all_round_liquidity(); -// // let (next_next_round_collateral3, next_next_round_unallocated3) = next_next_round -// // .get_all_round_liquidity(); -// -// // Assert initial state after converting position -> tokens -// assert(lp_collateral_init == 0, 'lp collat.init shd be in tokens'); -// assert( -// lp_unallocated_init == 0, 'lp unalloc.init shd be 0' -// ); // premiums already collected when position->tokens -// // assert(current_round_collateral_init == deposit_amount_wei, 'current r collat.init wrong'); -// //assert( -// // current_round_unallocated_init == 0, 'current r unalloc.init shd be 0' -// // ); // @dev should this be total premiums ? or stay 0 since all were collected -// // assert(next_round_collateral_init == 0, 'next round collat.init shd be 0'); -// // assert(next_round_unallocated_init == 0, 'next round unalloc.init shd b 0'); -// -// // Assert converting tokens -> position while current is Running deposits into the next round -// assert(lp_collateral1 == deposit_amount_wei / 4, 'conversion amount shd be collat'); -// assert(lp_unallocated1 == 0, 'lp unalloc1 shd be 0'); -// // assert(current_round_collateral1 == deposit_amount_wei, 'current r collat1 wrong'); -// // assert(current_round_unallocated1 == 0, 'current r unalloc1 shd be 0'); -// // assert(next_round_collateral1 == 0, 'next round collat1 shd be 0'); -// // assert(next_round_unallocated1 == 0, 'next round unalloc1 shd be 0'); -// -// // Assert converting tokens -> position while current is Settled deposits into the next round -// assert(lp_collateral2 == 0, 'liq is unalloc in next round'); -// assert(lp_unallocated2 == deposit_amount_wei / 2, 'prev collat shd rollover'); -//// assert(current_round_collateral2 == 0, 'current r collat2 shd b 0'); -//// assert(current_round_unallocated2 == 0, 'current r unalloc2 shd b 0'); -//// assert(next_round_collateral2 == 0, 'next round collat2 shd b 0'); -//// assert(next_round_unallocated2 == deposit_amount_wei / 2, 'all liq shd be unalloc in next'); -// -//// Assert converting tokens -> position while current is Auctioning deposits into the next next round -//// assert(lp_collateral3 == 3 * deposit_amount_wei / 4, 'lp collat shd rollover + amount'); -//// assert(lp_unallocated3 == 0, 'lp unalloc shd be 0'); -//// assert(current_round_collateral3 == 0, 'current r collat shd be 0'); -//// assert(current_round_unallocated3 == 0, 'current r unalloc shd be 0'); -//// assert(next_round_collateral3 == 3 * deposit_amount_wei / 4, 'round collat shd rollover'); -//// assert(next_round_unallocated3 == 0, 'round unalloc shd be 0'); -//// assert(next_next_round_collateral3 == 0, 'next next round collat shd be 0'); -//// assert(next_next_round_unallocated3 == 0, 'next next round unalloc shd b 0'); -//} -// -//// Test converting round lp tokens into a position backwards fails (only if user can choose target round) -////fn test_convert_lp_tokens_to_position_backwards_fails() { } -// -//// Test converting lp tokens into a position does not count the premiums earned in the source round -//#[test] -//#[available_gas(50000000)] -//#[ignore] -//fn test_convert_lp_tokens_to_position_does_not_count_source_round_premiums() { // -//// Deploy vault -// -//// LP1 and LP2 deposits into round 1 (50/50) -// -//// Start/end round 1's auction -// -//// LP1 converts entire position to lp tokens, then sends them to LP3 -// -//// Accelerate to current round 3 -// -//// LP3 converts all their r1 lp tokens -> r3 position -// -//// Assert LP3's r3 position is < LP2's r3 position (LP3's position does not include the premiums from r1 rolling over, but LP2's does) -//// assert(vault::position[LP3, 3] < vault::position[LP2, 3]) -//} -// -// -//// Test converting lp tokens into a position in same round (r1 tokens to r1 position) sets premiumsCollected to true -//// - The minter of the rx tokens already collected their premiums, this ensures that if the buyer of the tokens converts the rx tokens -//// into an rx position, they are not allowed to double-collect these premiums -//fn test_rx_tokens_to_rx_position_sets_rx_premiums_collected_to_true() { // -//// Deploy vault -// -//// LP1 deposits 20 ETH into round 1 -// -//// Start/end round 1's auction -// -//// Convert 10 ETH to r1 LP tokens -//// vault.convert_position_to_lp_tokens(amount: 10 ETH) -// -//// Split the tokens so that LP1 has 1/2, and LP2 has the other half -//// lp_token_dispatcher.transfer_from(LP1, LP2, 5 ETH r1 lp tokens) -// -//// LP1 and LP2 both have 5 ETH r1 lp tokens -// -//// Both convert their tokens to positions in round 1 -//// vault.convert_lp_tokens_to_position(lp_token_id: 1, target_round_for_position: 1, amount: 5 ETH LP tokens, caller: LP1) -//// vault.convert_lp_tokens_to_position(lp_token_id: 1, target_round_for_position: 1, amount: 5 ETH LP tokens, caller: LP2) -// -//// Assert positions are expected -//// assert(position[1, LP1] == 15 ETH) -//// assert(position[1, LP2] == 5 ETH) -// -//// Assert premiums are expected -//// @note Both LP's should not have any premiums to claim, when LP1 converted position -> token, they already collected the premiums for the tokens -//// assert(vault.premiums_balance_of(LP1) == 0) -//// assert(vault.premiums_balance_of(LP2) == 0) -// -//// What are premiumsCollected for both LP1 and LP2 ? -//// - -// -//// Convert all r1 LP tokens to an r1 position -//// vault.convert_lp_tokens_to_position(lp_token_id: 1, target_round_for_position: 1, amount: 5 ETH LP tokens) -// -//// Assert 5 ETH LP tokens were burned -// -//// Assert position[1] == 10ETH -// -//// premiumsCollected needs to be set, so that LP cannot double claim the r1 premium -// -//} -// -//// Test converting lp tokens into a position in the same round (r1 tokens to r1 position) handles exisiting premiums -//// @dev When rX tokens -> rX position, we set premiumsCollected to true. If the user has an active position with -//// collectable premiums, we do not want them to get lost, so we collect them during this step. -//fn test_rx_tokens_to_rx_position_handles_exisiting_premiums() { // -//// lp 1 and 2 deposit, auction starts/ends -//// lp 1 tokenizes and sells to lp2 -//// lp 2 converts the rX tokens into an rX position, -//// - test that lp2's already exisiting premiums get collected -//// - test that lp2's premiumsCollected is true -//} -//// @note Add test that converting tokens->position is always a position into the current round -//// @dev If we want to add that a user can choose the position round (rx tokens -> ry position), -//// then y must be > user.withdrawCheckpoint and we should check that other logic does not break. -//// @dev The 2 tests below are not needed if the conversion is always into the current round, only -//// if they user can specify the round when converting tokens->position -// -///// -///// LP Tokens (rA) -> LP tokens (rB) -///// -// -//// - Cannot go backwards (B must be > A) -//// - rB's auction must be over -//// - @dev LP tokens represent a position net premiums, so we do not count the premiums earned from an lp token's source round. -//// This is an issue because if a user converts tokenAs -> tokenBs, in the future we will ingore the premiums earned from roundB. To make sure the user -//// still gets their premiums from this round upon tokenA->tokenB conversion, we need to collect the premiums from roundB for the user -//// (we collect the amount of premiums the tokens earn in rB, if the user already has a storage position in rB with collectable premiums we ignore them). -//// - @dev When we collect rB's premiums, we do not touch premiumsCollected or collectable premiums in rB, they can stay as they are in storage -//// - @dev When we collect rB's premiums, we deposit them into roundB+1 as a position. -//// - We cannot collect the premiums as ETH since rB might be a historical (not the current) round and that ETH may be locked in the current round. If B is the current round, then -//// this position in rB+1 is immediately withdrawable. -// -//// @dev Begs the question, should collect always be a deposit into the next round, to then be withdrawable if choosen to (if user wants to claim premiums as eth, can just do a multi-call, collect, then withdraw) -//// - `collect()` should not always set premiumsCollected to true. If a user converts tokens A-B, we collect the rB premiums for the tokens. If the user has a position in storage with collectable premiums, we are -//// not touching them, so premiumsCollected does not need to be set. So maybe the collect entry point does set premiums collected to true, but collecting during a tokenA-tokenB conversion does not -// -// - - diff --git a/src/tests/misc/pitch_lake_test.cairo b/src/tests/misc/pitch_lake_test.cairo deleted file mode 100644 index e2856837..00000000 --- a/src/tests/misc/pitch_lake_test.cairo +++ /dev/null @@ -1,98 +0,0 @@ -// 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 - -// test 3 participants, 9 blocks? -// 0. collect liquidity -// 1. calculate volatility -// 2. get average basefee last month -// 3. calculate strike price for the next month -// 4. calculate premium for the reserve price -// 5. calculate collateral requirements -// 6. calculate cap -// 7. run batch auction -// 8. resolve batch auction -// 9. distribute options -// 10. calculate settlement price -// 11. calculate remaining liquidity -// 12. roll forward liquidity -// 13. allow for claims -// 14. allow liquidity redemption - -// product -// define schedule for roll forwards / liquidity redemptions / liquidity collection -// define Fossil usage -// define Fossil payments - -// silly simple things (1) -// deploy ETH liquidity -// * soooo -// * you need to remember the round at which you deployed liquidity -// * for each round a unit premium should be stored -// * so I can eploy 1 unit over three rounds where -// * it may be +0.01 +0.02 -0.1 -// * so -1.00 is all liq deployed and full payout? (no premium?) -// * -0.0x is partial liquidity deployed or partial payout? -// * -0.xy is sth like full payout, full liq but premium there -// redeem ETH liquidity - -// questions on ETH liq -// how to accept and store information on incoming ETH transfer -// need to read info on value transferred (ETH) -// ETH contract transfer -// will need approval for transfer in ERC20 -// approve and transfer? -// storage access -// let us lock some ETH soon -// will need to interact with another contract here? (ETH ERC20) -// a) allow only if liq deployments are being accepted -// b) record current liq deployment round, address and total liquidity -// bb) allow top ups -// c) when redeeming, need to define the round to redeem? -// cc) remodel later, no need to be smart here -// events: liq deployed addr round amount -// events: liq redeemed addr round amount (base, reward) - -use starknet::{ - ClassHash, ContractAddress, contract_address_const, deploy_syscall, - Felt252TryIntoContractAddress, get_contract_address, -}; -use openzeppelin::utils::serde::SerializedAppend; -use pitch_lake_starknet::{ - vault::{interface::{IVault, IVaultDispatcher, IVaultDispatcherTrait}}, - contracts::{ - pitch_lake::{ - IPitchLake, IPitchLakeDispatcher, IPitchLakeDispatcherTrait, IPitchLakeSafeDispatcher, - IPitchLakeSafeDispatcherTrait, PitchLake, - }, - }, - tests::utils::helpers::setup::{deploy_vault, deploy_market_aggregator, deploy_pitch_lake}, - types::{VaultType}, -}; -use debug::PrintTrait; - -// @note Make a tests/pitchlake/ directory for this ? -#[test] -#[available_gas(50000000)] -#[ignore] -fn test_vault_type() { - let pitch_lake_dispatcher: IPitchLakeDispatcher = deploy_pitch_lake(); - let itm_vault: IVaultDispatcher = pitch_lake_dispatcher.in_the_money_vault(); - let otm_vault: IVaultDispatcher = pitch_lake_dispatcher.out_the_money_vault(); - let atm_vault: IVaultDispatcher = pitch_lake_dispatcher.at_the_money_vault(); - assert(itm_vault.get_vault_type() == VaultType::InTheMoney, 'ITM vault wrong'); - assert(otm_vault.get_vault_type() == VaultType::OutOfMoney, 'OTM vault wrong'); - assert(atm_vault.get_vault_type() == VaultType::AtTheMoney, 'ATM vault wrong'); -} diff --git a/src/tests/misc/unallocated_liquidity_tests.cairo b/src/tests/misc/unallocated_liquidity_tests.cairo deleted file mode 100644 index 95a26208..00000000 --- a/src/tests/misc/unallocated_liquidity_tests.cairo +++ /dev/null @@ -1,195 +0,0 @@ -use pitch_lake_starknet::tests::{ - utils::{ - helpers::{ - accelerators::{ - accelerate_to_auctioning_custom, accelerate_to_running_custom, - accelerate_to_auctioning, accelerate_to_settled, accelerate_to_running - }, - general_helpers::{create_array_gradient, create_array_linear,}, setup::{setup_facade}, - }, - lib::{ - test_accounts::{ - liquidity_provider_1, liquidity_provider_2, liquidity_providers_get, - option_bidder_buyer_1, option_bidder_buyer_2, option_bidders_get, - }, - variables::{decimals}, - }, - facades::{ - option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait}, - vault_facade::{VaultFacade, VaultFacadeTrait}, - }, - }, -}; -// @note Return to this file post clean up to see if tests still needed - -// #[test] -// #[available_gas(10000000)] -// fn test_collected_liquidity_does_not_roll_over() { -// let (mut vault_facade, _) = setup_facade(); -// let mut option_round: OptionRoundFacade = vault_facade.get_next_round(); -// let params = option_round.get_params(); -// accelerate_to_running(ref vault_facade); -// //Get the total allocated liquidity at this stage - -// let (lp_allocated, _) = vault_facade.get_lp_balance_spread(liquidity_provider_1()); -// // Collect premium -// // Since no more deposits were made, unallocated is equal to the premiums from the auction - -// let claimable_premiums: u256 = params.total_options_available * params.reserve_price; -// vault_facade.withdraw(claimable_premiums, liquidity_provider_1()); - -// // The round has no more unallocated liquidity because lp withdrew it -// let unlocked_liqudity_after_premium_claim: u256 = vault_facade.get_total_unlocked(); -// assert(unlocked_liqudity_after_premium_claim == 0, 'premium should not roll over'); - -// // Settle option round with no payout -// accelerate_to_settled(ref vault_facade,params.strike_price - 1); -// // At this time, remaining liqudity was rolled to the next round (just initial deposit since there is no payout and premiums were collected) -// let mut next_option_round: OptionRoundFacade = vault_facade.get_next_round(); -// // Check rolled over amount is correct -// let next_round_unallocated: u256 = next_option_round.total_unallocated_liquidity(); -// assert(next_round_unallocated == lp_allocated, 'Rollover amount wrong'); -// } - -// Test that uncollected premiums roll over -// #[test] -// #[available_gas(10000000)] -// fn test_remaining_liqudity_rolls_over() { -// let (mut vault_facade, _) = setup_facade(); -// let mut option_round: OptionRoundFacade = vault_facade.get_next_round(); -// let params = option_round.get_params(); -// accelerate_to_running(ref vault_facade); -// //Get liquidity balance -// //@note Will include the premiums is unallocated and the locked deposit in allocated at this stage -// let (lp_allocated, lp_unallocated) = vault_facade.get_lp_balance_spread(liquidity_provider_1()); -// // Settle option round with no payout -// IMarketAggregatorSetterDispatcher { contract_address: vault_facade.get_market_aggregator() } -// .set_current_base_fee(params.strike_price - 1); -// vault_facade.timeskip_and_settle_round(); -// // At this time, remaining liqudity was rolled to the next round (initial deposit + premiums) -// let mut next_option_round: OptionRoundFacade = vault_facade.get_next_round(); -// // Check rolled over amount is correct -// let next_round_unallocated: u256 = next_option_round.total_unallocated_liquidity(); -// assert(next_round_unallocated == lp_allocated + lp_unallocated, 'Rollover amount wrong'); -// // } - -// #[test] -// #[available_gas(10000000)] -// fn test_premium_collection_ratio_conversion_unallocated_pool_1() { -// let (mut vault_facade, _) = setup_facade(); -// let mut current_round: OptionRoundFacade = vault_facade.get_current_round(); - -// // Deposit liquidity -// let deposit_amount_wei_1: u256 = 1000 * decimals(); -// let deposit_amount_wei_2: u256 = 10000 * decimals(); -// vault_facade.deposit(deposit_amount_wei_1, liquidity_provider_1()); -// vault_facade.deposit(deposit_amount_wei_2, liquidity_provider_2()); - -// let liquidity_providers = liquidity_providers_get(5); -// let deposit_amounts = create_array_gradient(1000 * decimals(), 1000 * decimals(), 5); -// let total_options_available (was total_deposts before) = accelerate_to_auctioning_custom( -// ref vault_facade, liquidity_providers.span(), deposit_amounts.span() -// ); -// let params = current_round.get_params(); -// // Make bid (ob1) -// let bid_amount: u256 = params.total_options_available; - -// let option_bidders = option_bidders_get(5); -// let bid_prices = create_array_linear(params.reserve_price, 5); -// let bid_amounts = create_array_linear(params.reserve_price * bid_amount, 5); -// let clearing_price = accelerate_to_running_custom( -// ref vault_facade, option_bidders.span(), bid_prices.span(), bid_amounts.span() -// ); - -// // Premium comes from unallocated pool -// let total_collateral: u256 = current_round.total_collateral(); -// let total_premium_to_be_paid: u256 = current_round.get_auction_clearing_price() -// * current_round.total_options_sold(); -// // LP % of the round -// // @note Check math is correct/precision is handled (goes for all instances of ratio calculations like this) -// let ratio_of_liquidity_provider_1: u256 = (vault_facade -// .get_collateral_balance_for(liquidity_provider_1()) -// * 100) -// / total_collateral; -// let ratio_of_liquidity_provider_2: u256 = (vault_facade -// .get_collateral_balance_for(liquidity_provider_2()) -// * 100) -// / total_collateral; -// // LP premiums share -// let premium_for_liquidity_provider_1: u256 = (ratio_of_liquidity_provider_1 -// * total_premium_to_be_paid) -// / 100; -// let premium_for_liquidity_provider_2: u256 = (ratio_of_liquidity_provider_2 -// * total_premium_to_be_paid) -// / 100; -// // The actual unallocated balance of the LPs -// let actual_unallocated_balance_provider_1: u256 = vault_facade -// .get_unallocated_balance_for(liquidity_provider_1()); -// let actual_unallocated_balance_provider_2: u256 = vault_facade -// .get_unallocated_balance_for(liquidity_provider_2()); - -// assert( -// actual_unallocated_balance_provider_1 == premium_for_liquidity_provider_1, -// 'premium paid in ratio' -// ); -// assert( -// actual_unallocated_balance_provider_2 == premium_for_liquidity_provider_2, -// 'premium paid in ratio' -// ); -// } - -// #[test] -// #[available_gas(10000000)] -// fn test_premium_collection_ratio_conversion_unallocated_pool_2() { -// let (mut vault_facade, _) = setup_facade(); -// let mut current_round: OptionRoundFacade = vault_facade.get_current_round(); -// let params = current_round.get_params(); -// // Deposit liquidity -// let deposit_amount_wei_1: u256 = 1000 * decimals(); -// vault_facade.deposit(deposit_amount_wei_1, liquidity_provider_1()); -// vault_facade.deposit(deposit_amount_wei_1, liquidity_provider_2()); -// // Make bid -// vault_facade.start_auction(); -// let bid_amount_user_1: u256 = ((params.total_options_available / 2) + 1) * params.reserve_price; -// let bid_amount_user_2: u256 = (params.total_options_available / 2) * params.reserve_price; -// current_round.place_bid(bid_amount_user_1, params.reserve_price, option_bidder_buyer_1()); -// current_round.place_bid(bid_amount_user_2, params.reserve_price, option_bidder_buyer_2()); -// // End auction -// vault_facade.timeskip_and_end_auction(); -// // Premium comes from unallocated pool -// let total_collateral: u256 = current_round.total_collateral(); -// let total_premium_to_be_paid: u256 = current_round.get_auction_clearing_price() -// * current_round.total_options_sold(); -// // LP % of the round -// let ratio_of_liquidity_provider_1: u256 = (vault_facade -// .get_collateral_balance_for(liquidity_provider_1()) -// * 100) -// / total_collateral; -// let ratio_of_liquidity_provider_2: u256 = (vault_facade -// .get_collateral_balance_for(liquidity_provider_2()) -// * 100) -// / total_collateral; -// // LP premiums share -// let premium_for_liquidity_provider_1: u256 = (ratio_of_liquidity_provider_1 -// * total_premium_to_be_paid) -// / 100; -// let premium_for_liquidity_provider_2: u256 = (ratio_of_liquidity_provider_2 -// * total_premium_to_be_paid) -// / 100; -// // The actual unallocated balance of the LPs -// let actual_unallocated_balance_provider_1: u256 = vault_facade -// .get_unallocated_balance_for(liquidity_provider_1()); -// let actual_unallocated_balance_provider_2: u256 = vault_facade -// .get_unallocated_balance_for(liquidity_provider_2()); - -// assert( -// actual_unallocated_balance_provider_1 == premium_for_liquidity_provider_1, -// 'premium paid in ratio' -// ); -// assert( -// actual_unallocated_balance_provider_2 == premium_for_liquidity_provider_2, -// 'premium paid in ratio' -// ); -// } - - diff --git a/src/tests/option_round/option_buyers/bidding_tests.cairo b/src/tests/option_round/option_buyers/bidding_tests.cairo index 77da6764..5f77f5fa 100644 --- a/src/tests/option_round/option_buyers/bidding_tests.cairo +++ b/src/tests/option_round/option_buyers/bidding_tests.cairo @@ -4,9 +4,9 @@ use starknet::{ Felt252TryIntoContractAddress, get_contract_address, get_block_timestamp, testing::{set_block_timestamp, set_contract_address} }; -use openzeppelin::{token::erc20::interface::{ERC20ABIDispatcherTrait,},}; -use pitch_lake_starknet::{ - library::eth::Eth, types::{OptionRoundState, Errors, BidDisplay}, +use openzeppelin_token::erc20::interface::ERC20ABIDispatcherTrait; +use pitch_lake::{ + library::eth::Eth, vault::{ contract::Vault, interface::{ @@ -14,7 +14,8 @@ use pitch_lake_starknet::{ } }, option_round::{ - contract::OptionRound, interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait}, + contract::OptionRound::Errors, + interface::{OptionRoundState, IOptionRoundDispatcher, IOptionRoundDispatcherTrait}, }, tests::{ utils::{ @@ -49,7 +50,8 @@ use pitch_lake_starknet::{ use debug::PrintTrait; // Test PartialOrd & PartialEq for MockBid by printing varying scenarios -// @note Test is ignored by default, to run the test run `scarb test -f test_bid_sort --include-ignored` +// @note Test is ignored by default, to run the test run `scarb test -f test_bid_sort +// --include-ignored` //#[test] //#[available_gas(50000000)] //#[ignore] @@ -75,9 +77,11 @@ use debug::PrintTrait; // let r = rhs.pop_front().unwrap(); // ("({}, {}) == ({}, {}): {}", l.amount, l.price, r.amount, r.price, l == r); // println!("({}, {}) < ({}, {}): {}", l.amount, l.price, r.amount, r.price, l < r); -// println!("({}, {}) <= ({}, {}): {}", l.amount, l.price, r.amount, r.price, l <= r); +// println!("({}, {}) <= ({}, {}): {}", l.amount, l.price, r.amount, r.price, l <= +// r); // println!("({}, {}) > ({}, {}): {}", l.amount, l.price, r.amount, r.price, l > r); -// println!("({}, {}) >= ({}, {}): {}", l.amount, l.price, r.amount, r.price, l >= r); +// println!("({}, {}) >= ({}, {}): {}", l.amount, l.price, r.amount, r.price, l >= +// r); // }, // Option::None => { break (); } // } @@ -86,31 +90,22 @@ use debug::PrintTrait; /// Failues /// -// @note Test is redundant, testing below reserve price covers this case as well -//// Test bidding 0 amount is rejected -//#[test] -//#[available_gas(10000000)] -//fn test_bid_amount_0_gets_rejected() { -// let (mut vault, _) = setup_facade(); -// let _options_available = accelerate_to_auctioning(ref vault); -// -// // Bid 0 amount -// let mut current_round = vault.get_current_round(); -// let reserve_price = current_round.get_reserve_price(); -// let bidder = option_bidder_buyer_1(); -// let bid_price = 2 * reserve_price; -// let bid_amount = 0; -// clear_event_logs(array![current_round.contract_address()]); -// 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 -// ); -// } -// } -//} +// Test bidding 0 amount is rejected +#[test] +#[available_gas(30000000)] +fn test_bid_amount_0_gets_rejected() { + let (mut vault, _) = setup_facade(); + let _options_available = accelerate_to_auctioning(ref vault); + + // Bid 0 amount + let mut current_round = vault.get_current_round(); + let reserve_price = current_round.get_reserve_price(); + let bidder = option_bidder_buyer_1(); + let bid_price = 2 * reserve_price; + let bid_amount = 0; + + current_round.place_bid_expect_error(bid_amount, bid_price, bidder, Errors::BidAmountZero); +} // Test bidding price < reserve fails (covers 0 amount as well since 0 is always < reserve price) #[test] 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 7a47a240..14c47e1c 100644 --- a/src/tests/option_round/option_buyers/exercise_options_tests.cairo +++ b/src/tests/option_round/option_buyers/exercise_options_tests.cairo @@ -1,8 +1,8 @@ use core::traits::Into; use starknet::testing::{set_block_timestamp, set_contract_address}; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; -use pitch_lake_starknet::{ - types::{Errors}, +use openzeppelin_token::erc20::interface::{ERC20ABIDispatcherTrait,}; +use pitch_lake::{ + option_round::contract::OptionRound::Errors, tests::{ utils::{ helpers::{ @@ -148,7 +148,8 @@ fn test_exercise_options_eth_transfer() { } // @note Add tests for get_payout_balance_for becoming 0 after exercise // @note Add test that options are burned when exercised -// @note Add test that OB can send options to another account then exercise (original owner shd not have access to payout afterwards) +// @note Add test that OB can send options to another account then exercise (original owner shd not +// have access to payout afterwards) // @note Add test that OB1 can exercise options, then receive more from OB2, the exerice again 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 170e89bb..b6370be7 100644 --- a/src/tests/option_round/option_buyers/refunding_bids_tests.cairo +++ b/src/tests/option_round/option_buyers/refunding_bids_tests.cairo @@ -1,8 +1,8 @@ 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::{ - types::{Errors}, +use openzeppelin_token::erc20::interface::{ERC20ABI, ERC20ABIDispatcher, ERC20ABIDispatcherTrait,}; +use pitch_lake::{ + option_round::contract::OptionRound::Errors, tests::{ utils::{ helpers::{ @@ -34,8 +34,9 @@ use pitch_lake_starknet::{ }; // @note Break up into separate files -// - pending/refundable bids tests can be in the same file, needs to move to option_round/state_transition/auction_end/, -// - refunding bids should be in a new file (option_round/option_buyers/refunding_bids_tests.cairo) +// - pending/refundable bids tests can be in the same file, needs to move to +// option_round/state_transition/auction_end/, - refunding bids should be in a new file +// (option_round/option_buyers/refunding_bids_tests.cairo) /// Test Setup /// 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 cf3d820b..72c2947c 100644 --- a/src/tests/option_round/option_buyers/tokenizing_options_tests.cairo +++ b/src/tests/option_round/option_buyers/tokenizing_options_tests.cairo @@ -1,5 +1,5 @@ -use pitch_lake_starknet::{ - types::Errors, +use pitch_lake::{ + option_round::contract::OptionRound::Errors, tests::{ utils::{ helpers::{ @@ -7,7 +7,8 @@ use pitch_lake_starknet::{ accelerate_to_running_custom, accelerate_to_auctioning, timeskip_and_end_auction, }, - setup::{setup_facade, deploy_custom_option_round}, + setup::{setup_facade, //deploy_custom_option_round + }, general_helpers::{ to_wei, to_wei_multi, get_erc20_balance, assert_two_arrays_equal_length }, @@ -16,7 +17,7 @@ use pitch_lake_starknet::{ lib::{test_accounts::{option_bidders_get, option_bidder_buyer_1},}, facades::{ vault_facade::{VaultFacade, VaultFacadeTrait}, - option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait, OptionRoundParams} + option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait} }, }, } @@ -68,7 +69,8 @@ fn test_tokenizing_options_mints_option_tokens() { current_round.contract_address(), *bidder ); - // Check that the user's erc20 option balance increases by the number of options minted + // Check that the user's erc20 option balance increases by the number of options + // minted assert( option_erc20_balance_after == option_erc20_balance_before + options_minted, 'wrong option erc20 balance' @@ -96,7 +98,7 @@ fn test_tokenizing_options_events() { assert_event_options_tokenized( current_round.contract_address(), *bidder, options_minted ); - // User's option erc20 balance after tokenizing + // User's option erc20 balance after tokenizing }, Option::None => { break (); }, } @@ -146,7 +148,8 @@ fn test_tokenizing_options_twice_does_nothing() { current_round.contract_address(), *bidder ); - // Check that the user's erc20 option balance increases by the number of options minted + // Check that the user's erc20 option balance increases by the number of options + // minted assert( option_erc20_balance_after == option_erc20_balance_before, 'wrong option erc20 balance' @@ -158,7 +161,8 @@ fn test_tokenizing_options_twice_does_nothing() { } // Test tokenizing options sets option_balance to 0 -// @note Discuss if this is the expected behavior, or if option_balance shd include storage + erc20 balances ? +// @note Discuss if this is the expected behavior, or if option_balance shd include storage + erc20 +// balances ? #[test] #[available_gas(500000000)] fn test_tokenizing_options_sets_option_storage_balance_to_0() { 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 343b4ca0..435ff13a 100644 --- a/src/tests/option_round/option_buyers/update_bids_tests.cairo +++ b/src/tests/option_round/option_buyers/update_bids_tests.cairo @@ -1,6 +1,6 @@ use core::traits::Into; -use pitch_lake_starknet::{ - types::{Errors, BidDisplay}, +use pitch_lake::{ + option_round::contract::OptionRound::Errors, tests::{ utils::{ helpers::{ @@ -187,7 +187,8 @@ fn test_updating_bids_lower_tree_index_loses() { let bidder2 = option_bidder_buyer_2(); let amount = (3 * options_available) / 4; - // Bid 1 amount > Bid 2 amount, Bid 1 price < Bid 2 price, Bid 2 is ranked higher because of price + // Bid 1 amount > Bid 2 amount, Bid 1 price < Bid 2 price, Bid 2 is ranked higher because of + // price let bid1 = current_round.place_bid(amount, reserve_price, bidder); let _bid2 = current_round.place_bid(amount / 2, reserve_price + 1, bidder2); 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 50780430..03c05d97 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::types::Bid; +use pitch_lake::types::Bid; #[starknet::interface] trait IRBTreeMockContract { @@ -12,8 +12,8 @@ trait IRBTreeMockContract { #[starknet::contract] mod RBTreeMockContract { - use pitch_lake_starknet::library::red_black_tree::RBTreeComponent; - use pitch_lake_starknet::types::Bid; + use pitch_lake::library::red_black_tree::RBTreeComponent; + use pitch_lake::types::Bid; component!(path: RBTreeComponent, storage: rb_tree, event: RBTreeEvent); 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 35dcbe67..19b230ce 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,5 +1,5 @@ use core::pedersen::pedersen; -use pitch_lake_starknet::{ +use pitch_lake::{ types::Bid, tests::{ utils::helpers::setup::setup_rb_tree_test, @@ -93,47 +93,43 @@ 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.bid_id); + inserted_node_ids.append(new_bid.bid_id); - let bid = rb_tree.find(new_bid.bid_id); - println!("Inserting price {}", bid.price); + let bid = rb_tree.find(new_bid.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.bid_id == 0, 'Bid delete error'); + assert(found_bid.bid_id == 0, 'Bid delete error'); - let is_tree_valid = rb_tree.is_tree_valid(); - assert(is_tree_valid, 'Tree is not valid'); + 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 a06741c5..ed1ccd8b 100644 --- a/src/tests/option_round/rb_tree/rb_tree_tests.cairo +++ b/src/tests/option_round/rb_tree/rb_tree_tests.cairo @@ -1,6 +1,6 @@ use core::pedersen::pedersen; use starknet::{contract_address_const, ContractAddress}; -use pitch_lake_starknet::{ +use pitch_lake::{ types::{Bid}, tests::{ option_round::{ @@ -1264,13 +1264,12 @@ 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)>) { @@ -1280,13 +1279,12 @@ 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/auction_end/clearing_price_tests.cairo b/src/tests/option_round/state_transition/auction_end/clearing_price_tests.cairo index 0ddb9295..c049dd3a 100644 --- a/src/tests/option_round/state_transition/auction_end/clearing_price_tests.cairo +++ b/src/tests/option_round/state_transition/auction_end/clearing_price_tests.cairo @@ -1,4 +1,4 @@ -use pitch_lake_starknet::{ +use pitch_lake::{ tests::{ utils::{ helpers::{ @@ -20,7 +20,7 @@ use pitch_lake_starknet::{ }, facades::{ vault_facade::{VaultFacade, VaultFacadeTrait}, - option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait, OptionRoundParams}, + option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait}, }, }, } @@ -88,7 +88,8 @@ fn test_clearing_price_is_highest_price_to_sell_all_options() { let total_options_available = accelerate_to_auctioning(ref vault); let mut current_round = vault.get_current_round(); - // Make bids, 4 bidders bid for 1/3 total options each, each bidder outbidding the previous one's price + // Make bids, 4 bidders bid for 1/3 total options each, each bidder outbidding the previous + // one's price let bidders = option_bidders_get(4).span(); let bid_amounts = create_array_linear(total_options_available / 3 + 1, bidders.len()).span(); let bid_prices = create_array_gradient(current_round.get_reserve_price(), 1, bidders.len()) diff --git a/src/tests/option_round/state_transition/auction_end/option_distribution_tests.cairo b/src/tests/option_round/state_transition/auction_end/option_distribution_tests.cairo index 69229a1e..1cf630d0 100644 --- a/src/tests/option_round/state_transition/auction_end/option_distribution_tests.cairo +++ b/src/tests/option_round/state_transition/auction_end/option_distribution_tests.cairo @@ -1,17 +1,17 @@ use starknet::{ contract_address_const, ContractAddress, testing::{set_block_timestamp, set_contract_address} }; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; -use pitch_lake_starknet::types::Consts::BPS; -use pitch_lake_starknet::tests::{ +use openzeppelin_token::erc20::interface::{ERC20ABIDispatcherTrait,}; +use pitch_lake::types::Consts::BPS; +use pitch_lake::tests::{ utils::{ helpers::{ accelerators::{ accelerate_to_auctioning, accelerate_to_auctioning_custom, accelerate_to_running, accelerate_to_running_custom, timeskip_and_settle_round, timeskip_and_end_auction, - //accelerate_to_running_custom_option_round, + //accelerate_to_running_custom_option_round, }, - setup::{setup_facade, setup_test_auctioning_bidders, deploy_custom_option_round}, + setup::{setup_facade, setup_test_auctioning_bidders}, general_helpers::{ pow, to_wei, to_wei_multi, sum_u256_array, create_array_linear, create_array_gradient, create_array_gradient_reverse, @@ -28,7 +28,7 @@ use pitch_lake_starknet::tests::{ }, facades::{ vault_facade::{VaultFacade, VaultFacadeTrait}, - option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait, OptionRoundParams} + option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait} }, }, }; @@ -216,7 +216,8 @@ fn test_bidding_higher_price_beats_higher_total_bid_amount() { let mut current_round = vault.get_current_round(); // Each bidder out bids the other's price, but with a lower amount - // @dev i.e The last bidder bids the highest price, but the other bidders bid a higher total eth (amount * price) + // @dev i.e The last bidder bids the highest price, but the other bidders bid a higher total eth + // (amount * price) let bid_amounts = create_array_gradient_reverse( total_options_available + 10, 1, number_of_option_bidders @@ -364,7 +365,8 @@ fn test_remaining_bids_go_to_last_bidder() { // let bid_price = current_round.get_reserve_price(); // // accelerate_to_running_custom( -// ref vault_facade, option_bidders.span(), array![bid_amount].span(), array![bid_price].span() +// ref vault_facade, option_bidders.span(), array![bid_amount].span(), +// array![bid_price].span() // ); // // assert(bid_amount == current_round.total_options_sold(), 'options sold wrong'); @@ -390,7 +392,8 @@ fn test_remaining_bids_go_to_last_bidder() { // let bid_price = reserve_price; // // accelerate_to_running_custom( -// ref vault_facade, option_bidders.span(), array![bid_amount].span(), array![bid_price].span() +// ref vault_facade, option_bidders.span(), array![bid_amount].span(), +// array![bid_price].span() // ); // // // Check all options sell @@ -455,7 +458,8 @@ fn test_losing_bid_gets_no_options() { // Deposit liquidity and start the auction let mut current_round = vault.get_current_round(); - // Make bids, 5 bidders bid for 1/3 total options each, each bidder outbidding the previous one's price + // Make bids, 5 bidders bid for 1/3 total options each, each bidder outbidding the previous + // one's price let mut bid_amounts = create_array_linear( total_options_available / (number_of_option_bidders - 1).into(), option_bidders.len() ) diff --git a/src/tests/option_round/state_transition/auction_end/premium_earned_tests.cairo b/src/tests/option_round/state_transition/auction_end/premium_earned_tests.cairo index 517ad6eb..3b4e3425 100644 --- a/src/tests/option_round/state_transition/auction_end/premium_earned_tests.cairo +++ b/src/tests/option_round/state_transition/auction_end/premium_earned_tests.cairo @@ -1,6 +1,6 @@ use starknet::testing::{set_block_timestamp, set_contract_address, ContractAddress}; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; -use pitch_lake_starknet::tests::{ +use openzeppelin_token::erc20::interface::{ERC20ABIDispatcherTrait,}; +use pitch_lake::tests::{ utils::{ helpers::{ event_helpers::{assert_event_transfer, assert_event_vault_withdrawal}, @@ -19,7 +19,7 @@ use pitch_lake_starknet::tests::{ variables::{decimals}, }, facades::{ - option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait, OptionRoundParams}, + option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait}, vault_facade::{VaultFacade, VaultFacadeTrait}, }, }, @@ -105,7 +105,8 @@ fn test_premium_amount_for_liquidity_providers_4() { _test_premiums_collectable_helper(ref vault_facade, liquidity_providers.span(), amounts.span()); } -// Test the portion of premiums an LP can collect in a round is correct, when deposit 1 >>> deposit 2 +// Test the portion of premiums an LP can collect in a round is correct, when deposit 1 >>> deposit +// 2 #[test] #[available_gas(50000000)] fn test_premium_amount_for_liquidity_providers_5() { @@ -130,12 +131,14 @@ fn test_premium_amount_for_liquidity_providers_5() { // // Deposit amounts // let amounts = array![50 * decimals(), 50 * decimals()]; -// _test_premiums_collectable_helper(ref vault_facade, liquidity_providers.span(), amounts.span()); +// _test_premiums_collectable_helper(ref vault_facade, liquidity_providers.span(), +// amounts.span()); // // LP balances pre collection // let lp1_balance_init = eth.balance_of(*liquidity_providers,[0]); // let lp2_balance_init = eth.balance_of(*liquidity_providers,[1]); -// let collectable_premiums = vault_facade.get_unallocated_balance_for(*liquidity_providers,[0]); // same as lp2 +// let collectable_premiums = +// vault_facade.get_unallocated_balance_for(*liquidity_providers,[0]); // same as lp2 // // Collect premiums // vault_facade.collect_premiums(*liquidity_providers,[0]); @@ -148,17 +151,20 @@ fn test_premium_amount_for_liquidity_providers_5() { // // Check eth: current_round -> liquidity_providers // assert( -// lp1_balance_final == lp1_balance_init + collectable_premiums, 'lp1 did not collect premiums' +// lp1_balance_final == lp1_balance_init + collectable_premiums, 'lp1 did not collect +// premiums' // ); // assert( -// lp2_balance_final == lp2_balance_init + collectable_premiums, 'lp2 did not collect premiums' +// lp2_balance_final == lp2_balance_init + collectable_premiums, 'lp2 did not collect +// premiums' // ); // } // @note Add test that premiums earned are sent to vault (eth transfer) // @note Add test that premiums go to vault::unlocked & vault::lp::unlocked -// @note Not needed since there is no longer a collect premiums function, just the single withdraw function +// @note Not needed since there is no longer a collect premiums function, just the single withdraw +// function //#[test] //#[available_gas(10000000)] //fn test_premium_collection_emits_events() { @@ -167,7 +173,8 @@ fn test_premium_amount_for_liquidity_providers_5() { // // @note Replace with accelerators post sync // let liquidity_providers = array![liquidity_provider_1(), liquidity_provider_2(),]; // let amounts = array![50 * decimals(), 50 * decimals()]; -// _test_premiums_collectable_helper(ref vault_facade, liquidity_providers.span(), amounts.span()); +// _test_premiums_collectable_helper(ref vault_facade, liquidity_providers.span(), +// amounts.span()); // let mut option_round = vault_facade.get_current_round(); // // Clear events // clear_event_logs(array![vault_facade.contract_address(), option_round.contract_address()]); @@ -208,18 +215,22 @@ fn test_premium_amount_for_liquidity_providers_5() { // // Deposit amounts // let amounts = array![50 * decimals(), 50 * decimals()]; -// _test_premiums_collectable_helper(ref vault_facade, liquidity_providers.span(), amounts.span()); +// _test_premiums_collectable_helper(ref vault_facade, liquidity_providers.span(), +// amounts.span()); // // Unallocated balances pre collection // let mut current_round = vault_facade.get_current_round(); // let round_unallocated_init = current_round.total_unallocated_liquidity(); -// let lp2_unallocated_init = vault_facade.get_unallocated_balance_for(*liquidity_providers,[1]); +// let lp2_unallocated_init = +// vault_facade.get_unallocated_balance_for(*liquidity_providers,[1]); // // Collect premiums (lp 1 only) // vault_facade.collect_premiums(*liquidity_providers,[0]); // // Unallocated balaances post collection -// let lp1_unallocated_final = vault_facade.get_unallocated_balance_for(*liquidity_providers,[0]); -// let lp2_unallocated_final = vault_facade.get_unallocated_balance_for(*liquidity_providers,[1]); +// let lp1_unallocated_final = +// vault_facade.get_unallocated_balance_for(*liquidity_providers,[0]); +// let lp2_unallocated_final = +// vault_facade.get_unallocated_balance_for(*liquidity_providers,[1]); // let round_unallocated_final = current_round.total_unallocated_liquidity(); // // Check unallocated balances for round/liquidity_providers is correct @@ -231,8 +242,8 @@ fn test_premium_amount_for_liquidity_providers_5() { // ); // } -// @note This is essentailly testing withdraw amount > unlocked balance, so should be a withdraw test, -// but we still should test LP cannot double collect prmeiums +// @note This is essentailly testing withdraw amount > unlocked balance, so should be a withdraw +// test, but we still should test LP cannot double collect prmeiums // Test collecting premiums twice fails // @note Maybe this shouldnt fail, but just do nothing instead ? // #[test] @@ -245,7 +256,8 @@ fn test_premium_amount_for_liquidity_providers_5() { // let amounts = array![50 * decimals()]; // // Deposit amounts -// _test_premiums_collectable_helper(ref vault_facade, liquidity_providers.span(), amounts.span()); +// _test_premiums_collectable_helper(ref vault_facade, liquidity_providers.span(), +// amounts.span()); // // Collect premiums // vault_facade.collect_premiums(*liquidity_providers,[0]); @@ -298,6 +310,7 @@ fn _test_premiums_collectable_helper( i += 1; }; } -// @note Need tests for premium collection: lp/round unallocated decrementing, remaining premiums for other LPs unaffected, cannot collect twice/more than remaining collectable amount +// @note Need tests for premium collection: lp/round unallocated decrementing, remaining premiums +// for other LPs unaffected, cannot collect twice/more than remaining collectable amount diff --git a/src/tests/option_round/state_transition/auction_end/refundable_bids_tests.cairo b/src/tests/option_round/state_transition/auction_end/refundable_bids_tests.cairo index 4e65f7ed..4a602a50 100644 --- a/src/tests/option_round/state_transition/auction_end/refundable_bids_tests.cairo +++ b/src/tests/option_round/state_transition/auction_end/refundable_bids_tests.cairo @@ -1,7 +1,7 @@ use core::traits::TryInto; use starknet::{ContractAddress, testing::{set_block_timestamp, set_contract_address}}; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; -use pitch_lake_starknet::tests::{ +use openzeppelin_token::erc20::interface::ERC20ABIDispatcherTrait; +use pitch_lake::tests::{ utils::{ helpers::{ event_helpers::{assert_event_unused_bids_refunded, clear_event_logs}, @@ -129,7 +129,8 @@ fn test_refundable_bids_includes_partial_and_fully_refunded_bids() { let total_options_available = accelerate_to_auctioning(ref vault); let mut current_round = vault.get_current_round(); - // Same bidder places 4 bids, first 2 are fully used, 3rd is partially used, and 4th is fully unused + // Same bidder places 4 bids, first 2 are fully used, 3rd is partially used, and 4th is fully + // unused let bidder = option_bidder_buyer_1(); let bidders = create_array_linear(bidder, 4).span(); let bid_amount = 2 * total_options_available / 5; diff --git a/src/tests/option_round/state_transition/auction_end/unsold_liquidity_tests.cairo b/src/tests/option_round/state_transition/auction_end/unsold_liquidity_tests.cairo index 4e238875..181a0034 100644 --- a/src/tests/option_round/state_transition/auction_end/unsold_liquidity_tests.cairo +++ b/src/tests/option_round/state_transition/auction_end/unsold_liquidity_tests.cairo @@ -1,4 +1,4 @@ -use pitch_lake_starknet::{ +use pitch_lake::{ tests::{ utils::{ helpers::{ @@ -23,7 +23,7 @@ use pitch_lake_starknet::{ }, facades::{ vault_facade::{VaultFacade, VaultFacadeTrait}, - option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait, OptionRoundParams}, + option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait}, }, }, } 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 4220a92c..adb43cf5 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 @@ -2,15 +2,20 @@ use starknet::{ get_block_timestamp, ContractAddress, contract_address_const, testing::{set_contract_address, set_block_timestamp} }; -use pitch_lake_starknet::{ - vault::contract::Vault, types::{Errors}, +use pitch_lake::{ + vault::contract::Vault, vault::contract::Vault::Errors as vErrors, + option_round::contract::OptionRound::Errors, + vault::interface::{L1Data, L1DataRequest, L1Result, VaultType}, + option_round::interface::PricingData, library::pricing_utils, tests::{ utils::{ helpers::{ accelerators::{ - accelerate_to_auctioning, accelerate_to_running, accelerate_to_running_custom + accelerate_to_auctioning, accelerate_to_running, accelerate_to_running_custom, + accelerate_to_settled, clear_event_logs, }, - setup::{setup_facade}, + setup::{get_fossil_address, setup_facade, deploy_vault, deploy_eth}, + general_helpers::{to_gwei}, event_helpers::{assert_event_pricing_data_set}, }, lib::{ test_accounts::{ @@ -21,15 +26,16 @@ use pitch_lake_starknet::{ }, facades::{ vault_facade::{VaultFacade, VaultFacadeTrait}, - option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait, OptionRoundParams}, - market_aggregator_facade::{MarketAggregatorFacade, MarketAggregatorFacadeTrait}, + option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait}, }, }, } }; const salt: u64 = 0x123; + const err: felt252 = Errors::CallerIsNotVault; +const err2: felt252 = vErrors::CallerNotWhitelisted; fn not_vault() -> ContractAddress { contract_address_const::<'not vault'>() @@ -76,20 +82,24 @@ fn test_only_vault_can_settle_option_round() { current_round.settle_option_round_expect_error(0x123, err); } +fn get_random_pricing_data_points() -> PricingData { + PricingData { strike_price: to_gwei(5829), cap_level: 20084, reserve_price: to_gwei(482745), } +} #[test] #[available_gas(50000000)] fn test_only_vault_can_update_round_params() { let (mut vault, _) = setup_facade(); let mut round = vault.get_current_round(); + let data = get_random_pricing_data_points(); set_contract_address(not_vault()); - round.update_round_params_expect_error(err); + round.set_pricing_data_expect_err(data, err); } #[test] #[available_gas(50000000)] -fn test_update_round_params_on_round() { +fn test_set_pricing_data_on_round() { let (mut vault, _) = setup_facade(); let mut round = vault.get_current_round(); @@ -97,69 +107,60 @@ fn test_update_round_params_on_round() { let cap_level0 = round.get_cap_level(); let strike_price0 = round.get_strike_price(); + let random_pricing_data = get_random_pricing_data_points(); set_contract_address(vault.contract_address()); - round.update_params(1, 2, 3); + round.set_pricing_data(random_pricing_data.clone()); let reserve_price = round.get_reserve_price(); let cap_level = round.get_cap_level(); let strike_price = round.get_strike_price(); - assert_eq!(reserve_price, 1); - assert_eq!(cap_level, 2); - assert_eq!(strike_price, 3); + // Check params change assert(reserve_price != reserve_price0, 'reserve price did not change'); assert(cap_level != cap_level0, 'cap level did not change'); assert(strike_price != strike_price0, 'strike price did not change'); + // Check params are changed correctly + assert_eq!(reserve_price, random_pricing_data.reserve_price); + assert_eq!(cap_level, random_pricing_data.cap_level); + assert_eq!(strike_price, random_pricing_data.strike_price); } #[test] #[available_gas(50000000)] -fn test_update_round_params_on_vault() { - let (mut vault, _) = setup_facade(); - let mut round = vault.get_current_round(); +fn test_setting_first_round_params_updates_from_default() { + set_block_timestamp(123456789); + let eth = deploy_eth(); + let vault_dispatcher = deploy_vault(VaultType::AtTheMoney, eth.contract_address); + let mut vault: VaultFacade = VaultFacade { vault_dispatcher }; + let mut current_round = vault.get_current_round(); - let reserve_price0 = round.get_reserve_price(); - let cap_level0 = round.get_cap_level(); - let strike_price0 = round.get_strike_price(); - let mk_agg = vault.get_market_aggregator_facade(); - let volatility0 = mk_agg - .get_volatility_for_round(vault.contract_address(), round.get_round_id()) - .unwrap(); - - // Mock values on mk agg - let new_reserve_price = 1; - let new_cap_level = 2; - let new_strike_price = 3; - let new_volatility = 4; - - mk_agg - .set_reserve_price_for_round( - vault.contract_address(), round.get_round_id(), new_reserve_price - ); - mk_agg.set_cap_level_for_round(vault.contract_address(), round.get_round_id(), new_cap_level); - mk_agg.set_volatility_for_round(vault.contract_address(), round.get_round_id(), new_volatility); - - let to = round.get_auction_start_date(); - let from = to - Vault::TWAP_DURATION; - // @note Only works because vautl is default ATM - mk_agg.set_TWAP_for_time_period(from, to, new_strike_price); - - vault.update_round_params(); + let strike_price0 = current_round.get_strike_price(); + let cap_level0 = current_round.get_cap_level(); + let reserve_price0 = current_round.get_reserve_price(); - let reserve_price = round.get_reserve_price(); - let cap_level = round.get_cap_level(); - let strike_price = round.get_strike_price(); - let volatility = mk_agg - .get_volatility_for_round(vault.contract_address(), round.get_round_id()) - .unwrap(); - - assert_eq!(reserve_price, new_reserve_price); - assert_eq!(cap_level, new_cap_level); - assert_eq!(strike_price, new_strike_price); - assert_eq!(volatility, new_volatility); - assert(reserve_price != reserve_price0, 'reserve price did not change'); - assert(cap_level != cap_level0, 'cap level did not change'); - assert(strike_price != strike_price0, 'strike price did not change'); - assert(volatility != volatility0, 'volatility did not change'); -} + let request = vault.get_request_to_start_auction(); + let result = L1Result { + proof: array![].span(), + data: L1Data { twap: to_gwei(10), volatility: 5000, reserve_price: to_gwei(123), } + }; + set_contract_address(get_fossil_address()); + vault.fulfill_request(request, result); + + let strike_price = current_round.get_strike_price(); + let cap_level = current_round.get_cap_level(); + let reserve_price = current_round.get_reserve_price(); + + assert_eq!(reserve_price0, 0); + assert_eq!(strike_price0, 0); + assert_eq!(cap_level0, 0); + + let ex_cap_level = pricing_utils::calculate_cap_level(0, 5000); + let ex_strike_price = pricing_utils::calculate_strike_price( + VaultType::AtTheMoney, to_gwei(10), 5000 + ); + + assert_eq!(reserve_price, to_gwei(123)); + assert_eq!(cap_level, ex_cap_level); + assert_eq!(strike_price, ex_strike_price); +} diff --git a/src/tests/option_round/state_transition/fulfill_request_tests.cairo b/src/tests/option_round/state_transition/fulfill_request_tests.cairo new file mode 100644 index 00000000..b8b487ef --- /dev/null +++ b/src/tests/option_round/state_transition/fulfill_request_tests.cairo @@ -0,0 +1,184 @@ +use starknet::{ + get_block_timestamp, ContractAddress, contract_address_const, + testing::{set_contract_address, set_block_timestamp} +}; +use pitch_lake::{ + vault::contract::Vault, vault::contract::Vault::Errors as vErrors, + option_round::contract::OptionRound::Errors, + vault::interface::{L1Data, L1DataRequest, L1Result, VaultType}, + option_round::interface::PricingData, library::pricing_utils, + tests::{ + utils::{ + helpers::{ + accelerators::{ + accelerate_to_auctioning, accelerate_to_running, accelerate_to_running_custom, + accelerate_to_settled, clear_event_logs, timeskip_past_option_expiry_date, + }, + setup::{get_fossil_address, setup_facade, deploy_vault, deploy_eth}, + general_helpers::{to_gwei}, event_helpers::{assert_event_pricing_data_set}, + }, + lib::{ + test_accounts::{ + liquidity_provider_1, option_bidder_buyer_1, option_bidder_buyer_2, + option_bidder_buyer_3, option_bidder_buyer_4, option_bidders_get, + }, + variables::{decimals}, + }, + facades::{ + vault_facade::{VaultFacade, VaultFacadeTrait}, + option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait}, + }, + }, + } +}; + +const err: felt252 = vErrors::CallerNotWhitelisted; + +#[test] +#[available_gas(1_000_000_000)] +fn test_only_fossil_can_fulfill_request() { + let (mut vault, _) = setup_facade(); + + accelerate_to_auctioning(ref vault); + accelerate_to_running(ref vault); + accelerate_to_settled(ref vault, to_gwei(10)); + + let fossil = get_fossil_address(); + + set_contract_address(fossil); + let request = vault.get_request_to_start_auction(); + let result = L1Result { + proof: array![].span(), + data: L1Data { twap: to_gwei(666), volatility: 1234, reserve_price: to_gwei(123) } + }; + vault.fulfill_request(request, result); + set_contract_address(contract_address_const::<'not fossil'>()); + vault.fulfill_request_expect_error(request, result, err); +} + +#[test] +#[available_gas(1_000_000_000)] +fn test_request_must_fulfill_to_start_auction() { + let (mut vault, _) = setup_facade(); + + accelerate_to_auctioning(ref vault); + accelerate_to_running(ref vault); + accelerate_to_settled(ref vault, to_gwei(10)); + + vault.start_auction_expect_error(Errors::PricingDataNotSet); +} +// timeskip_past_option_expiry_data + +#[test] +#[available_gas(1_000_000_000)] +fn test_request_must_fulfill_to_settle_round() { + let (mut vault, _) = setup_facade(); + + accelerate_to_auctioning(ref vault); + accelerate_to_running(ref vault); + timeskip_past_option_expiry_date(ref vault); + + vault.settle_option_round_expect_error(Errors::PricingDataNotSet); +} + +#[test] +#[available_gas(1_000_000_000)] +fn test_set_pricing_data_events() { + let (mut vault, _) = setup_facade(); + + for _ in 0 + ..3_usize { + accelerate_to_auctioning(ref vault); + accelerate_to_running(ref vault); + accelerate_to_settled(ref vault, to_gwei(10)); + + let mut current_round = vault.get_current_round(); + clear_event_logs(array![current_round.contract_address()]); + let request = vault.get_request_to_start_auction(); + let twap = to_gwei(10); + let volatility = 3333; + let reserve_price = to_gwei(3); + let response = L1Result { + proof: array![].span(), data: L1Data { twap, volatility, reserve_price } + }; + set_contract_address(get_fossil_address()); + vault.fulfill_request(request, response); + + assert_event_pricing_data_set( + current_round.contract_address(), twap, volatility, reserve_price + ); + } +} + +#[test] +#[available_gas(1_000_000_000)] +fn test_fulfilling_multiple_request() { + let (mut vault, _) = setup_facade(); + + for _ in 0 + ..3_usize { + accelerate_to_auctioning(ref vault); + accelerate_to_running(ref vault); + accelerate_to_settled(ref vault, to_gwei(10)); + + // Refresh data + let exp_twap0 = to_gwei(666); + let exp_volatility0 = 1234; + let exp_reserve_price0 = to_gwei(123); + let exp_cap_level0 = pricing_utils::calculate_cap_level(0, exp_volatility0); + let exp_strike_price0 = pricing_utils::calculate_strike_price( + VaultType::AtTheMoney, exp_twap0, exp_cap_level0 + ); + + let request = vault.get_request_to_start_auction(); + let response = L1Result { + proof: array![].span(), + data: L1Data { + twap: exp_twap0, volatility: exp_volatility0, reserve_price: exp_reserve_price0 + } + }; + set_contract_address(get_fossil_address()); + vault.fulfill_request(request, response); + + let mut current_round = vault.get_current_round(); + let strike_price0 = current_round.get_strike_price(); + let cap_level0 = current_round.get_cap_level(); + let reserve_price0 = current_round.get_reserve_price(); + + // Check pricing data updates as expected + assert_eq!(cap_level0, exp_cap_level0); + assert_eq!(reserve_price0, exp_reserve_price0); + assert_eq!(strike_price0, exp_strike_price0); + + // Refresh data again + let exp_twap1 = to_gwei(777); + let exp_volatility1 = 6789; + let exp_reserve_price1 = to_gwei(333); + let exp_cap_level1 = pricing_utils::calculate_cap_level(123, exp_volatility1); + let exp_strike_price1 = pricing_utils::calculate_strike_price( + VaultType::AtTheMoney, exp_twap1, exp_cap_level1 + ); + + let request = vault.get_request_to_start_auction(); + let response = L1Result { + proof: array![].span(), + data: L1Data { + twap: exp_twap1, volatility: exp_volatility1, reserve_price: exp_reserve_price1 + } + }; + set_contract_address(get_fossil_address()); + vault.fulfill_request(request, response); + + let strike_price1 = current_round.get_strike_price(); + let cap_level1 = current_round.get_cap_level(); + let reserve_price1 = current_round.get_reserve_price(); + + // Check pricing data updates as expected + assert_eq!(cap_level1, exp_cap_level1); + assert_eq!(reserve_price1, exp_reserve_price1); + assert_eq!(strike_price1, exp_strike_price1); + } +} + +// @note todo add test for request timestamp out of bounds (high and low for both fulfillment types (auction start/round settle)) +// @note todo add test for setting default values then starting auction/settling round 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 dabd981e..e20ae66f 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,4 +1,4 @@ -use pitch_lake_starknet::{ +use pitch_lake::{ types::Consts::BPS, library::utils::{min, max}, tests::{ utils::{ @@ -34,7 +34,8 @@ fn calculate_expected_payout(ref round: OptionRoundFacade, settlement_price: u25 } } -// @note These tests should move to ./src/tests/option_round/state_transition/option_settled_tests.cairo +// @note These tests should move to +// ./src/tests/option_round/state_transition/option_settled_tests.cairo /// Total Payout Tests /// #[test] @@ -54,7 +55,7 @@ fn test_option_payout_amount_index_higher_than_strike() { let (mut vault, mut current_round) = setup_test_running(); let K = current_round.get_strike_price(); - let x = 11111; // +1.111% strike + let x = 11111; // K * 1.1111 let settlement_price = (x * K) / BPS; let expected_payout = calculate_expected_payout(ref current_round, settlement_price); @@ -62,6 +63,7 @@ fn test_option_payout_amount_index_higher_than_strike() { // Check payout balance is expected assert(payout == expected_payout, 'payout doesnt match expected'); + let diff = settlement_price - K; assert(payout == current_round.total_options_sold() * diff, 'asdf'); } diff --git a/src/tests/utils/facades/lp_token_facade.cairo b/src/tests/utils/facades/lp_token_facade.cairo deleted file mode 100644 index 8ce0363f..00000000 --- a/src/tests/utils/facades/lp_token_facade.cairo +++ /dev/null @@ -1,31 +0,0 @@ -use starknet::{ContractAddress,}; -use pitch_lake_starknet::contracts::{ - lp_token::{ILPToken, ILPTokenDispatcher, ILPTokenDispatcherTrait}, -}; - -#[derive(Drop)] -struct LPTokenFacade { - lp_token_dispatcher: ILPTokenDispatcher, -} - -#[generate_trait] -impl LPTokenFacadeImpl of LPTokenFacadeTrait { - /// Reads /// - fn contract_address(ref self: LPTokenFacade) -> ContractAddress { - self.lp_token_dispatcher.contract_address - } - - fn vault_address(ref self: LPTokenFacade) -> ContractAddress { - self.lp_token_dispatcher.vault_address() - } - - fn option_round_address(ref self: LPTokenFacade) -> ContractAddress { - self.lp_token_dispatcher.option_round_address() - } -/// Writes /// - -// @note token -> position & position -> token should be vault entry points - -/// Helpers /// -} - diff --git a/src/tests/utils/facades/market_aggregator_facade.cairo b/src/tests/utils/facades/market_aggregator_facade.cairo deleted file mode 100644 index 966e21f2..00000000 --- a/src/tests/utils/facades/market_aggregator_facade.cairo +++ /dev/null @@ -1,85 +0,0 @@ -use starknet::ContractAddress; -use pitch_lake_starknet::{ - market_aggregator::{ - contract::{MarketAggregator}, types::{DataTypes, PeriodTypes}, - interface::{ - IMarketAggregatorDispatcher, IMarketAggregatorDispatcherTrait, - IMarketAggregatorMockDispatcher, IMarketAggregatorMockDispatcherTrait, - } - }, -}; - - -#[derive(Drop, Copy)] -struct MarketAggregatorFacade { - contract_address: ContractAddress -} - -#[generate_trait] -impl MarketAggregatorFacadeImpl of MarketAggregatorFacadeTrait { - // Helpers - fn get_dispatcher(self: @MarketAggregatorFacade) -> IMarketAggregatorDispatcher { - IMarketAggregatorDispatcher { contract_address: *self.contract_address } - } - - fn get_mock_dispatcher(self: @MarketAggregatorFacade) -> IMarketAggregatorMockDispatcher { - IMarketAggregatorMockDispatcher { contract_address: *self.contract_address } - } - - // Getters - fn get_TWAP_for_block_period( - self: @MarketAggregatorFacade, from: u64, to: u64 - ) -> Option { - self.get_dispatcher().get_TWAP_for_block_period(from, to) - } - - fn get_TWAP_for_time_period(self: @MarketAggregatorFacade, from: u64, to: u64) -> Option { - self.get_dispatcher().get_TWAP_for_time_period(from, to) - } - - fn get_reserve_price_for_round( - self: @MarketAggregatorFacade, vault_address: ContractAddress, round_id: u256 - ) -> Option { - self.get_dispatcher().get_reserve_price_for_round(vault_address, round_id) - } - - fn get_volatility_for_round( - self: @MarketAggregatorFacade, vault_address: ContractAddress, round_id: u256 - ) -> Option { - self.get_dispatcher().get_volatility_for_round(vault_address, round_id) - } - - fn get_cap_level_for_round( - self: @MarketAggregatorFacade, vault_address: ContractAddress, round_id: u256 - ) -> Option { - self.get_dispatcher().get_cap_level_for_round(vault_address, round_id) - } - - // Setters - fn set_TWAP_for_block_period(self: @MarketAggregatorFacade, from: u64, to: u64, value: u256) { - self.get_mock_dispatcher().set_TWAP_for_block_period(from, to, value); - } - - fn set_TWAP_for_time_period(self: @MarketAggregatorFacade, from: u64, to: u64, value: u256) { - self.get_mock_dispatcher().set_TWAP_for_time_period(from, to, value); - } - - fn set_reserve_price_for_round( - self: @MarketAggregatorFacade, vault_address: ContractAddress, round_id: u256, value: u256 - ) { - self.get_mock_dispatcher().set_reserve_price_for_round(vault_address, round_id, value); - } - - fn set_cap_level_for_round( - self: @MarketAggregatorFacade, vault_address: ContractAddress, round_id: u256, value: u128 - ) { - self.get_mock_dispatcher().set_cap_level_for_round(vault_address, round_id, value); - } - - fn set_volatility_for_round( - self: @MarketAggregatorFacade, vault_address: ContractAddress, round_id: u256, value: u128 - ) { - self.get_mock_dispatcher().set_volatility_for_round(vault_address, round_id, value); - } -} - diff --git a/src/tests/utils/facades/option_round_facade.cairo b/src/tests/utils/facades/option_round_facade.cairo index ec6c51e9..d9658196 100644 --- a/src/tests/utils/facades/option_round_facade.cairo +++ b/src/tests/utils/facades/option_round_facade.cairo @@ -1,17 +1,17 @@ //Helper functions for posterity -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin_token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; use starknet::{ContractAddress, testing::{set_contract_address}}; -use pitch_lake_starknet::{ - types::{VaultType, Errors, Bid, OptionRoundState, OptionRoundConstructorParams, Consts::BPS,}, +use pitch_lake::{ + types::{Errors, Bid, Consts::BPS}, option_round::{ interface::{ - IOptionRoundDispatcher, IOptionRoundDispatcherTrait, IOptionRoundSafeDispatcher, - IOptionRoundSafeDispatcherTrait, + OptionRoundState, IOptionRoundDispatcher, IOptionRoundDispatcherTrait, + IOptionRoundSafeDispatcher, IOptionRoundSafeDispatcherTrait, PricingData } }, vault::{ interface::{ - IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, + VaultType, IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, IVaultSafeDispatcherTrait, }, contract::Vault, @@ -20,17 +20,15 @@ use pitch_lake_starknet::{ utils::{ helpers::{ setup::eth_supply_and_approve_all_bidders, - general_helpers::{assert_two_arrays_equal_length, get_erc20_balance}, + general_helpers::{to_gwei, assert_two_arrays_equal_length, get_erc20_balance}, accelerators::{accelerate_to_auctioning_custom}, }, lib::{ - test_accounts::{vault_manager, bystander, liquidity_provider_1}, - structs::{OptionRoundParams} - }, - facades::{ - sanity_checks, market_aggregator_facade::{MarketAggregatorFacadeTrait}, - vault_facade::{VaultFacade, VaultFacadeTrait}, + test_accounts::{ + vault_manager, bystander, liquidity_provider_1 + }, //structs::{OptionRoundParams} }, + facades::{sanity_checks, vault_facade::{VaultFacade, VaultFacadeTrait},}, } }, }; @@ -59,17 +57,17 @@ impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { // Update the params of the option round // @note this sets strike price as well, meaning this function is only to be used where the // the strike is irrelevant to the test (i.e only in option distribution tests) - fn update_params( - ref self: OptionRoundFacade, reserve_price: u256, cap_level: u128, strike_price: u256 - ) { + fn set_pricing_data(ref self: OptionRoundFacade, pricing_data: PricingData) { // Force update the params in the round - self.option_round_dispatcher.update_round_params(reserve_price, cap_level, strike_price); + self.option_round_dispatcher.set_pricing_data(pricing_data); } #[feature("safe_dispatcher")] - fn update_round_params_expect_error(ref self: OptionRoundFacade, error: felt252) { + fn set_pricing_data_expect_err( + ref self: OptionRoundFacade, pricing_data: PricingData, error: felt252 + ) { let safe = self.get_safe_dispatcher(); - safe.update_round_params(1, 2, 3).expect_err(error); + safe.set_pricing_data(pricing_data).expect_err(error); } @@ -80,17 +78,21 @@ impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { options_available: u256, reserve_price: u256, ) { - // Calculate what starting liquidity == options_available - // M = L/ CL - // L = M * CL - let strike_price = 1000000000; // 1 gwei - let cap_level: u128 = 5000; // 50.00 % above strike + // The number of options (M) that can be sold is the total liquidity (L) divided by the max + // payout per option (Capped) + // L / Capped = M + // L = M * Capped + let strike_price = to_gwei(10); // 10 gwei + let cap_level: u128 = 5000; // Max payout is 50.00 % above strike let capped_payout_per_option = (strike_price * cap_level.into()) / BPS; let starting_liquidity = (options_available * capped_payout_per_option); // Update the params of the option round + let pricing_data = PricingData { strike_price, cap_level, reserve_price, }; + + // Update the pricing data points as the Vault set_contract_address(vault.contract_address()); - self.update_params(reserve_price, cap_level, strike_price); + self.set_pricing_data(pricing_data); let total_options_available = accelerate_to_auctioning_custom( ref vault, array![liquidity_provider_1()].span(), array![starting_liquidity].span() @@ -378,6 +380,10 @@ impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { /// Dates + fn get_deployment_date(ref self: OptionRoundFacade) -> u64 { + self.option_round_dispatcher.get_deployment_date() + } + fn get_auction_start_date(ref self: OptionRoundFacade) -> u64 { self.option_round_dispatcher.get_auction_start_date() } @@ -390,6 +396,7 @@ impl OptionRoundFacadeImpl of OptionRoundFacadeTrait { self.option_round_dispatcher.get_option_settlement_date() } + /// $ fn starting_liquidity(ref self: OptionRoundFacade) -> u256 { diff --git a/src/tests/utils/facades/sanity_checks.cairo b/src/tests/utils/facades/sanity_checks.cairo index 918d00f1..b298cf76 100644 --- a/src/tests/utils/facades/sanity_checks.cairo +++ b/src/tests/utils/facades/sanity_checks.cairo @@ -1,6 +1,6 @@ use starknet::{ContractAddress, testing::{set_contract_address}}; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; -use pitch_lake_starknet::{ +use openzeppelin_token::erc20::interface::{ERC20ABIDispatcherTrait,}; +use pitch_lake::{ types::Bid, option_round::{interface::IOptionRoundDispatcherTrait}, tests::{ utils::{ diff --git a/src/tests/utils/facades/vault_facade.cairo b/src/tests/utils/facades/vault_facade.cairo index eb6f7e7f..8c2e5ca2 100644 --- a/src/tests/utils/facades/vault_facade.cairo +++ b/src/tests/utils/facades/vault_facade.cairo @@ -1,21 +1,16 @@ use starknet::{ContractAddress, testing::{set_contract_address, set_block_timestamp}}; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; -use pitch_lake_starknet::{ +use openzeppelin_token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use pitch_lake::{ vault::{ interface::{ - IVaultDispatcher, IVaultDispatcherTrait, IVaultSafeDispatcher, IVaultSafeDispatcherTrait + L1DataRequest, L1Result, IVaultDispatcher, IVaultDispatcherTrait, IVaultSafeDispatcher, + IVaultSafeDispatcherTrait } }, option_round::{ contract::OptionRound, interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,} }, - market_aggregator::{ - contract::{MarketAggregator}, types::{DataTypes, PeriodTypes}, - interface::{ - IMarketAggregatorDispatcher, IMarketAggregatorDispatcherTrait, - IMarketAggregatorMockDispatcher, IMarketAggregatorMockDispatcherTrait - } - }, + library::constants::{ROUND_TRANSITION_PERIOD, AUCTION_RUN_TIME, OPTION_RUN_TIME}, tests::{ utils::{ lib::{ @@ -23,12 +18,10 @@ use pitch_lake_starknet::{ variables::{decimals}, }, facades::{ - option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait}, - market_aggregator_facade::{MarketAggregatorFacade, MarketAggregatorFacadeTrait}, - sanity_checks, + option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait}, sanity_checks, }, helpers::{ - setup::eth_supply_and_approve_all_bidders, + setup::{eth_supply_and_approve_all_bidders, get_fossil_address}, general_helpers::{assert_two_arrays_equal_length} }, }, @@ -151,9 +144,17 @@ impl VaultFacadeImpl of VaultFacadeTrait { /// State transition - // Force a refresh of the round's params - fn update_round_params(ref self: VaultFacade) { - self.vault_dispatcher.update_round_params(); + fn fulfill_request(ref self: VaultFacade, request: L1DataRequest, result: L1Result) { + set_contract_address(get_fossil_address()); + self.vault_dispatcher.fulfill_request(request, result); + } + + #[feature("safe_dispatcher")] + fn fulfill_request_expect_error( + ref self: VaultFacade, request: L1DataRequest, result: L1Result, error: felt252 + ) { + let safe_vault = self.get_safe_dispatcher(); + safe_vault.fulfill_request(request, result).expect_err(error); } fn start_auction(ref self: VaultFacade) -> u256 { @@ -190,15 +191,14 @@ impl VaultFacadeImpl of VaultFacadeTrait { 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(); // Settle the current round + let mut current_round = self.get_current_round(); let total_payout = self.vault_dispatcher.settle_round(); + let total_payout = sanity_checks::settle_option_round(ref current_round, total_payout); - 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()); + let current_round_address = self.get_option_round_address(self.get_current_round_id()); + eth_supply_and_approve_all_bidders(current_round_address, self.get_eth_address()); total_payout } @@ -208,14 +208,17 @@ impl VaultFacadeImpl of VaultFacadeTrait { let safe_vault = self.get_safe_dispatcher(); safe_vault.settle_round().expect_err(error); } + /// Fossil - fn get_market_aggregator_facade(ref self: VaultFacade) -> MarketAggregatorFacade { - MarketAggregatorFacade { contract_address: self.get_market_aggregator() } + fn get_request_to_settle_round(ref self: VaultFacade) -> L1DataRequest { + self.vault_dispatcher.get_request_to_settle_round() } + fn get_request_to_start_auction(ref self: VaultFacade,) -> L1DataRequest { + self.vault_dispatcher.get_request_to_start_auction() + } - /// Reads /// /// Rounds @@ -431,32 +434,32 @@ impl VaultFacadeImpl of VaultFacadeTrait { /// Misc - // @note Would be a lot of grepping, but could match Dispatcher format by making this a struct member instead of an entry point + // @note Would be a lot of grepping, but could match Dispatcher format by making this a struct + // member instead of an entry point fn contract_address(ref self: VaultFacade) -> ContractAddress { self.vault_dispatcher.contract_address } - fn get_market_aggregator(ref self: VaultFacade) -> ContractAddress { - self.vault_dispatcher.get_market_aggregator_address() - } - // Eth contract address fn get_eth_address(ref self: VaultFacade) -> ContractAddress { self.vault_dispatcher.get_eth_address() } fn get_auction_run_time(ref self: VaultFacade) -> u64 { - self.vault_dispatcher.get_auction_run_time() + AUCTION_RUN_TIME + //self.vault_dispatcher.get_auction_run_time() } fn get_option_run_time(ref self: VaultFacade) -> u64 { - self.vault_dispatcher.get_option_run_time() + //self.vault_dispatcher.get_option_run_time() + OPTION_RUN_TIME } // Gets the round transition period in seconds, 3 hours is a random number for testing // @note TODO impl this in contract later fn get_round_transition_period(ref self: VaultFacade) -> u64 { - self.vault_dispatcher.get_round_transition_period() + //self.vault_dispatcher.get_round_transition_period() + ROUND_TRANSITION_PERIOD } } diff --git a/src/tests/utils/helpers/accelerators.cairo b/src/tests/utils/helpers/accelerators.cairo index 97275a0a..62d02484 100644 --- a/src/tests/utils/helpers/accelerators.cairo +++ b/src/tests/utils/helpers/accelerators.cairo @@ -3,23 +3,22 @@ use starknet::{ testing::{set_block_timestamp, set_contract_address} }; use core::fmt::Display; -use pitch_lake_starknet::{ - types::{OptionRoundState, VaultType, BidDisplay}, - vault::{contract::Vault, interface::{IVaultDispatcher, IVaultDispatcherTrait}}, - option_round::{ - contract::{OptionRound}, interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,}, - }, - market_aggregator::{ - contract::MarketAggregator, +use pitch_lake::{ + vault::{ + contract::Vault, interface::{ - IMarketAggregatorMock, IMarketAggregatorMockDispatcher, - IMarketAggregatorMockDispatcherTrait, + L1Result, L1Data, L1DataRequest, VaultType, IVaultDispatcher, IVaultDispatcherTrait } }, + option_round::{ + contract::{OptionRound}, + interface::{ + PricingData, OptionRoundState, IOptionRoundDispatcher, IOptionRoundDispatcherTrait, + }, + }, tests::{ utils::{ lib::{ - structs::{OptionRoundParams}, test_accounts::{ vault_manager, liquidity_provider_1, option_bidder_buyer_1, bystander, option_bidders_get, liquidity_providers_get, @@ -28,13 +27,12 @@ use pitch_lake_starknet::{ }, helpers::{ // accelerators::{accelerate_to_auction_custom_auction_params}, event_helpers::{clear_event_logs,}, - general_helpers::{assert_two_arrays_equal_length, get_erc20_balances}, - setup::{deploy_custom_option_round}, + general_helpers::{to_gwei, assert_two_arrays_equal_length, get_erc20_balances}, + //setup::{deploy_custom_option_round}, }, facades::{ option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait}, vault_facade::{VaultFacade, VaultFacadeTrait}, - market_aggregator_facade::{MarketAggregatorFacade, MarketAggregatorFacadeTrait}, }, }, }, @@ -92,14 +90,17 @@ fn accelerate_to_running_custom( /// Settling option round // Settle the option round with a custom settlement price (compared to strike to determine payout) -fn accelerate_to_settled(ref self: VaultFacade, TWAP: u256) -> u256 { - let mut current_round = self.get_current_round(); - let market_aggregator = self.get_market_aggregator_facade(); +fn accelerate_to_settled(ref self: VaultFacade, twap: u256) -> u256 { + // Get the data request to fulfill + let request = self.get_request_to_settle_round(); + + // Create a mock result (proofs do not matter) + let result = L1Result { + data: L1Data { twap, volatility: 5000, reserve_price: to_gwei(2) }, proof: array![].span() + }; - // Set the TWAP for the round's duration - let to = current_round.get_option_settlement_date(); - let from = to - Vault::TWAP_DURATION; - market_aggregator.set_TWAP_for_time_period(from, to, TWAP); + // Set repsonse + self.fulfill_request(request, result); // Jump to the option expiry date and settle the round timeskip_and_settle_round(ref self) @@ -125,7 +126,7 @@ fn timeskip_past_option_expiry_date(ref self: VaultFacade) { // Jump past the round transition period fn timeskip_past_round_transition_period(ref self: VaultFacade) { let now = get_block_timestamp(); - let round_transition_period = self.vault_dispatcher.get_round_transition_period(); + let round_transition_period = self.get_round_transition_period(); set_block_timestamp(now + round_transition_period); } diff --git a/src/tests/utils/helpers/event_helpers.cairo b/src/tests/utils/helpers/event_helpers.cairo index 2c17d725..5c19f5f2 100644 --- a/src/tests/utils/helpers/event_helpers.cairo +++ b/src/tests/utils/helpers/event_helpers.cairo @@ -1,17 +1,19 @@ use core::array::SpanTrait; use starknet::{testing, ContractAddress,}; -use openzeppelin::{ - utils::serde::SerializedAppend, token::erc20::{ERC20Component, ERC20Component::Transfer} -}; -use pitch_lake_starknet::{vault::contract::Vault, option_round::contract::OptionRound,}; +use openzeppelin_utils::serde::SerializedAppend; +use openzeppelin_token::erc20::{ERC20Component, ERC20Component::Transfer}; +use pitch_lake::{vault::contract::Vault, option_round::contract::OptionRound}; +use pitch_lake::option_round::interface::{PricingData}; use debug::PrintTrait; -// Helpers + +/// Helpers /// // Pop the earliest unpopped logged event for the contract as the requested type // and checks there's no more data left on the event, preventing unaccounted params. // Indexed event members are currently not supported, so they are ignored. fn pop_log, impl TEvent: starknet::Event>(address: ContractAddress) -> Option { let (mut keys, mut data) = testing::pop_log_raw(address)?; + //println!("keys: {:?}\ndata: {:?}", keys.clone(), data.clone()); let ret = starknet::Event::deserialize(ref keys, ref data); assert(data.is_empty(), 'Event has extra data'); ret @@ -46,13 +48,14 @@ fn assert_events_equal, +Drop>(actual: T, expected: T) { assert(actual == expected, 'Event does not match expected'); } -// OptionRound Events +/// OptionRound Events /// // Check AuctionStart emits correctly fn assert_event_auction_start( option_round_address: ContractAddress, starting_liquidity: u256, options_available: u256 ) { - // @note Confirm this works (fix from discord), then work into other event assertions (should handle manual ones as well) + // @note Confirm this works (fix from discord), then work into other event assertions (should + // handle manual ones as well) // @note Reminder to clear event logs at the end of the accelerators match pop_log::(option_round_address) { Option::Some(e) => { @@ -79,7 +82,8 @@ fn assert_event_auction_bid_placed( let expected = OptionRound::Event::BidPlaced( OptionRound::BidPlaced { account, bid_id, amount, price, bid_tree_nonce_now } ); - //println!("expected:\n{}\n{}\n{}\n{}\n{}", Into::::into(account), bid_id, amount, price, bid_tree_nonce_now); + //println!("expected:\n{}\n{}\n{}\n{}\n{}", Into::::into(account), bid_id, amount, price, bid_tree_nonce_now); assert_events_equal(e, expected); }, Option::None => { panic(array!['Could not find event']); }, @@ -104,21 +108,20 @@ fn assert_event_auction_bid_updated( } } -// Check AuctionRejectedBid emits correctly -//fn assert_event_auction_bid_rejected( -// contract: ContractAddress, account: ContractAddress, amount: u256, price: u256, -//) { -// match pop_log::(contract) { -// Option::Some(e) => { -// let expected = OptionRound::Event::BidRejected( -// OptionRound::BidRejected { account, amount, price } -// ); -// assert_events_equal(e, expected); -// }, -// Option::None => { panic(array!['Could not find event']); }, -// } -//} - +// Check PricingDataSet emits correctly +fn assert_event_pricing_data_set( + option_round_address: ContractAddress, strike_price: u256, cap_level: u128, reserve_price: u256, +) { + match pop_log::(option_round_address) { + Option::Some(e) => { + let expected = OptionRound::Event::PricingDataSet( + OptionRound::PricingDataSet { strike_price, cap_level, reserve_price } + ); + assert_events_equal(e, expected); + }, + Option::None => { panic(array!['No events found']); } + }; +} // Check AuctionEnd emits correctly fn assert_event_auction_end( option_round_address: ContractAddress, @@ -138,7 +141,6 @@ fn assert_event_auction_end( } // Check OptionSettle emits correctly -// @dev Settlment price is the price determining the payout for the round fn assert_event_option_settle( option_round_address: ContractAddress, settlement_price: u256, payout_per_option: u256, ) { @@ -171,7 +173,8 @@ fn assert_event_unused_bids_refunded( fn assert_event_options_tokenized( contract: ContractAddress, account: ContractAddress, minted_amount: u256 ) { - // We pop here twice since the method fires a ERC20 transfer event before the OptionsTokenized event + // We pop here twice since the method fires a ERC20 transfer event before the OptionsTokenized + // event match pop_log::(contract) { Option::Some(_) => { match pop_log::(contract) { @@ -205,11 +208,12 @@ fn assert_event_options_exercised( }; } -// ERC20 Events +/// ERC20 Events /// // Test transfer event (ERC20 structure) emits correctly -// @note Can remove all instances of this test that are testing eth transfers, just testing the balance changes is enough -// @note Add tests using this helper for erc20 transfer tests for options, lp tokens later +// @note Can remove all instances of this test that are testing eth transfers, just testing the +// balance changes is enough @note Add tests using this helper for erc20 transfer tests for options, +// lp tokens later fn assert_event_transfer( contract: ContractAddress, from: ContractAddress, to: ContractAddress, value: u256 ) { @@ -222,19 +226,42 @@ fn assert_event_transfer( } } -// Vault Events +/// Vault Events /// -// Test OptionRoundCreated event emits correctly fn assert_event_option_round_deployed( contract: ContractAddress, round_id: u256, address: ContractAddress, - reserve_price: u256, - strike_price: u256, - cap_level: u128, auction_start_date: u64, auction_end_date: u64, option_settlement_date: u64, + pricing_data: PricingData, +) { + match pop_log::(contract) { + Option::Some(_) => { + assert_event_option_round_deployed_single( + contract, + round_id, + address, + auction_start_date, + auction_end_date, + option_settlement_date, + pricing_data + ); + }, + Option::None => { panic(array!['No events found1']); } + } +} + +// Test OptionRoundCreated event emits correctly +fn assert_event_option_round_deployed_single( + contract: ContractAddress, + round_id: u256, + address: ContractAddress, + auction_start_date: u64, + auction_end_date: u64, + option_settlement_date: u64, + pricing_data: PricingData, ) { match pop_log::(contract) { Option::Some(e) => { @@ -242,17 +269,15 @@ fn assert_event_option_round_deployed( Vault::OptionRoundDeployed { round_id, address, - reserve_price, - strike_price, - cap_level, auction_start_date, auction_end_date, - option_settlement_date + option_settlement_date, + pricing_data } ); assert_events_equal(e, expected); }, - Option::None => { panic(array!['No events found']); } + Option::None => { panic(array!['No events found2']); } }; } @@ -271,6 +296,7 @@ fn assert_event_vault_deposit( account, amount, account_unlocked_balance_now, vault_unlocked_balance_now } ); + assert_events_equal(e, expected); }, Option::None => { panic(array!['No events found']); } diff --git a/src/tests/utils/helpers/general_helpers.cairo b/src/tests/utils/helpers/general_helpers.cairo index 1a21fff2..51d69d1f 100644 --- a/src/tests/utils/helpers/general_helpers.cairo +++ b/src/tests/utils/helpers/general_helpers.cairo @@ -1,9 +1,14 @@ -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait,}; +use openzeppelin_token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait,}; use starknet::{ContractAddress}; /// Array helpers /// -// Create array of length `len`, each element is `amount` (For bids use the function twice for price and amount) +fn to_gwei(value: u256) -> u256 { + value * 1_000_000_000 +} + +// Create array of length `len`, each element is `amount` (For bids use the function twice for price +// and amount) fn create_array_linear, +Copy>(amount: T, len: u32) -> Array { let mut arr = array![]; let mut index = 0; @@ -14,7 +19,8 @@ fn create_array_linear, +Copy>(amount: T, len: u32) -> Array { arr } -// Create array of length `len`, each element is `amount + index * step` (For bids use the function twice for price and amount) +// Create array of length `len`, each element is `amount + index * step` (For bids use the function +// twice for price and amount) fn create_array_gradient(amount: u256, step: u256, len: u32) -> Array { let mut arr: Array = array![]; let mut index: u32 = 0; @@ -25,7 +31,8 @@ fn create_array_gradient(amount: u256, step: u256, len: u32) -> Array { arr } -// Create array of length `len`, each element is `amount - index * step` (For bids use the function twice for price and amount) +// Create array of length `len`, each element is `amount - index * step` (For bids use the function +// twice for price and amount) fn create_array_gradient_reverse(amount: u256, step: u256, len: u32) -> Array { let mut arr: Array = array![]; let mut index: u32 = 0; diff --git a/src/tests/utils/helpers/setup.cairo b/src/tests/utils/helpers/setup.cairo index 99655867..ea26e5ef 100644 --- a/src/tests/utils/helpers/setup.cairo +++ b/src/tests/utils/helpers/setup.cairo @@ -1,36 +1,28 @@ use core::starknet::SyscallResultTrait; -use core::traits::Into; -use core::array::ArrayTrait; +use core::num::traits::Zero; use starknet::{ ClassHash, ContractAddress, contract_address_const, deploy_syscall, Felt252TryIntoContractAddress, get_contract_address, contract_address_try_from_felt252, testing, get_block_timestamp, testing::{set_block_timestamp, set_contract_address} }; -use openzeppelin::{ - utils::serde::SerializedAppend, - token::erc20::{ERC20Component, interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait,}} +use openzeppelin_utils::serde::SerializedAppend; +use openzeppelin_token::erc20::{ + ERC20Component, interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait} }; -use pitch_lake_starknet::{ - types::{OptionRoundState, VaultType}, library::{eth::Eth}, - vault::{contract::Vault, interface::{IVaultDispatcher, IVaultDispatcherTrait}}, + +use pitch_lake::{ + library::eth::Eth, vault::contract::Vault, + vault::interface::{ + ConstructorArgs, L1Data, L1DataRequest, L1Result, VaultType, IVaultDispatcher, + IVaultDispatcherTrait + }, + vault::contract::{Vault::{DAY}}, option_round::{ contract::OptionRound, interface::{ - IOptionRoundDispatcher, IOptionRoundDispatcherTrait, IOptionRoundSafeDispatcher, - IOptionRoundSafeDispatcherTrait, - }, - }, - market_aggregator::{ - contract::MarketAggregator, - interface::{ - IMarketAggregator, IMarketAggregatorDispatcher, IMarketAggregatorDispatcherTrait, - IMarketAggregatorSafeDispatcher, IMarketAggregatorSafeDispatcherTrait - } - }, - contracts::{ - pitch_lake::{ - IPitchLakeDispatcher, IPitchLakeSafeDispatcher, IPitchLakeDispatcherTrait, PitchLake, - IPitchLakeSafeDispatcherTrait + PricingData, ConstructorArgs as ConstructorArgsOptionRound, IOptionRoundDispatcher, + IOptionRoundDispatcherTrait, IOptionRoundSafeDispatcher, + IOptionRoundSafeDispatcherTrait, OptionRoundState }, }, tests::{ @@ -42,20 +34,20 @@ use pitch_lake_starknet::{ }, utils::{ lib::{ - structs::{OptionRoundParams}, + //structs::{OptionRoundParams}, test_accounts::{weth_owner, liquidity_providers_get, option_bidders_get, bystander}, variables::{week_duration, decimals}, }, helpers::{ accelerators::{ - accelerate_to_auctioning, accelerate_to_auctioning_custom, accelerate_to_running + accelerate_to_settled, accelerate_to_auctioning, + accelerate_to_auctioning_custom, accelerate_to_running }, event_helpers::{clear_event_logs}, general_helpers::{to_wei}, }, facades::{ option_round_facade::{OptionRoundFacade, OptionRoundFacadeTrait}, - vault_facade::{VaultFacade, VaultFacadeTrait}, - market_aggregator_facade::{MarketAggregatorFacade, MarketAggregatorFacadeTrait} + vault_facade::{VaultFacade, VaultFacadeImpl, VaultFacadeTrait}, }, }, }, @@ -82,42 +74,24 @@ fn deploy_eth() -> ERC20ABIDispatcher { return ERC20ABIDispatcher { contract_address }; } -// Deploy market aggregator for testing -fn deploy_market_aggregator() -> MarketAggregatorFacade { - let mut calldata = array![]; - let salt: felt252 = starknet::get_block_timestamp().into(); - - let (contract_address, _) = deploy_syscall( - MarketAggregator::TEST_CLASS_HASH.try_into().unwrap(), salt, calldata.span(), true - ) - .expect('DEPLOY_MARKET_AGGREGATOR_FAILED'); - - /// Mock mk agg values - // let mk_agg = MarketAggregatorFacade {contract_address}; - // let from = 0; - // let to: u64 = from + 'rtp'.try_into().unwrap() + 'art'.try_into().unwrap() + 'ord'.try_into().unwrap(); - // mk_agg.set_reserve_price_for_time_period(from, to, 4000000000); - // mk_agg.set_cap_level_for_time_period(from, to, 5000); - // mk_agg.set_TWAP_for_time_period(from, to, 300000000); - - // Clear the event log - clear_event_logs(array![contract_address]); - return MarketAggregatorFacade { contract_address }; +fn get_fossil_address() -> ContractAddress { + contract_address_const::<'REQUEST FULFILLER'>() } -// Deploy the vault and market aggregator -fn deploy_vault( - vault_type: VaultType, eth_address: ContractAddress, mk_agg_address: ContractAddress + +fn deploy_vault_with_events( + vault_type: VaultType, eth_address: ContractAddress ) -> IVaultDispatcher { /// Deploy market aggregator let mut calldata = array![]; - calldata.append_serde(1000); - calldata.append_serde(1000); - calldata.append_serde(1000); - calldata.append_serde(eth_address); - calldata.append_serde(vault_type); - calldata.append_serde(mk_agg_address); // needed ? - calldata.append_serde(OptionRound::TEST_CLASS_HASH); + let args = ConstructorArgs { + request_fulfiller: get_fossil_address(), + eth_address, + option_round_class_hash: OptionRound::TEST_CLASS_HASH.try_into().unwrap(), + vault_type, + }; + args.serialize(ref calldata); + // @dev Making salt timestamp dependent so we can easily deploy new instances for testing let now = get_block_timestamp(); let salt = 'some salt' + now.into(); @@ -127,51 +101,17 @@ fn deploy_vault( ) .expect('DEPLOY_VAULT_FAILED'); - // Clear the event log - clear_event_logs(array![contract_address]); - return IVaultDispatcher { contract_address }; } +// Deploy the vault and market aggregator +fn deploy_vault(vault_type: VaultType, eth_address: ContractAddress) -> IVaultDispatcher { + let vault = deploy_vault_with_events(vault_type, eth_address); -fn deploy_pitch_lake() -> IPitchLakeDispatcher { - let mut calldata = array![]; - let eth_dispatcher = deploy_eth(); - let eth_address = eth_dispatcher.contract_address; - - let mkagg1 = deploy_market_aggregator().contract_address; - starknet::testing::set_block_timestamp(1); - let mkagg2 = deploy_market_aggregator().contract_address; - starknet::testing::set_block_timestamp(2); - let mkagg3 = deploy_market_aggregator().contract_address; - - let ITM: IVaultDispatcher = deploy_vault(VaultType::InTheMoney, eth_address, mkagg1); - let OTM: IVaultDispatcher = deploy_vault(VaultType::OutOfMoney, eth_address, mkagg2); - let ATM: IVaultDispatcher = deploy_vault(VaultType::AtTheMoney, eth_address, mkagg3); - let mkagg = deploy_market_aggregator(); - - calldata.append_serde(ITM.contract_address); - calldata.append_serde(OTM.contract_address); - calldata.append_serde(ATM.contract_address); - calldata.append_serde(mkagg.contract_address); - - let (contract_address, _) = deploy_syscall( - PitchLake::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), true - ) - .expect('DEPLOY_AD_FAILED'); - - // Clear event logs - clear_event_logs( - array![ - ITM.contract_address, - OTM.contract_address, - ATM.contract_address, - mkagg.contract_address, - contract_address - ] - ); + // Clear the event log + clear_event_logs(array![vault.contract_address]); - return IPitchLakeDispatcher { contract_address }; + vault } fn setup_rb_tree_test() -> IRBTreeMockContractDispatcher { @@ -186,121 +126,52 @@ fn to_gwei(amount: u256) -> u256 { amount * 1_000_000_000 } -fn setup_facade_vault_type(vault_type: VaultType) -> (VaultFacade, MarketAggregatorFacade) { - /// Mock mk agg values - let mk_agg = deploy_market_aggregator(); - let now = 1000000000000; - starknet::testing::set_block_timestamp(now); +fn setup_facade_vault_type(vault_type: VaultType) -> VaultFacade { + set_block_timestamp(1234567890); - // Deploy eth and vault + // Deploy eth let eth_dispatcher: ERC20ABIDispatcher = deploy_eth(); + + // Deploy vault facade let vault_dispatcher: IVaultDispatcher = deploy_vault( - vault_type, eth_dispatcher.contract_address, mk_agg.contract_address + vault_type, eth_dispatcher.contract_address ); - - /// Mock market aggregator values for first 5 rounds - // Sets reserve price for each round to 2 gwei - // Sets cap level for each round to 5000 - // Sets volatility for each round to 3333 - // Each round will settle with 10 gwei TWAP - // Each round will use 10 gwei as the latest TWAP when round starts - let current_round_address = vault_dispatcher - .get_round_address(vault_dispatcher.get_current_round_id()); - let current_round = IOptionRoundDispatcher { contract_address: current_round_address }; - let mut TWAP_end = current_round.get_auction_start_date(); - let mut TWAP_start = TWAP_end - Vault::TWAP_DURATION; - let duration = vault_dispatcher.get_round_transition_period() - + vault_dispatcher.get_auction_run_time() - + vault_dispatcher.get_option_run_time(); - let mut i = 1; - while i <= 5 { - mk_agg.set_reserve_price_for_round(vault_dispatcher.contract_address, i, to_gwei(2)); - mk_agg.set_cap_level_for_round(vault_dispatcher.contract_address, i, 5000); - mk_agg.set_volatility_for_round(vault_dispatcher.contract_address, i, 3333); - - // @dev Set TWAP for time period up to auction start - mk_agg.set_TWAP_for_time_period(TWAP_start, TWAP_end, to_gwei(10)); - // @dev Set TWAP for time period up to round settle - let prev_round_settlement_date = TWAP_start - - vault_dispatcher.get_round_transition_period(); - let from = prev_round_settlement_date - Vault::TWAP_DURATION; - mk_agg.set_TWAP_for_time_period(from, prev_round_settlement_date, to_gwei(10)); - - TWAP_start += duration; - TWAP_end += duration; - i += 1; + let mut vault_facade = VaultFacade { vault_dispatcher }; + + // Set the + // Fulfill request to start auction + let request = vault_facade.get_request_to_start_auction(); + let result = L1Result { + data: L1Data { twap: to_gwei(10), volatility: 5000, reserve_price: to_gwei(2), }, + proof: array![].span() }; + vault_facade.fulfill_request(request, result); - vault_dispatcher.update_round_params(); - - // Supply eth to test accounts and approve vault to transfer lp eth + // @dev Supply eth to liquidity providers and approve vault for transferring eth eth_supply_and_approve_all_providers( vault_dispatcher.contract_address, eth_dispatcher.contract_address ); - //Supply and approve option_bidders + // @dev Supply eth to option buyers and approve current round for transferring eth let current_round_address = vault_dispatcher .get_round_address(vault_dispatcher.get_current_round_id()); eth_supply_and_approve_all_bidders(current_round_address, eth_dispatcher.contract_address); - // Supply eth to test accounts and approve option round 1 to spend ob eth - // Supply bystander with eth and approve vault to transfer eth - set_contract_address(weth_owner()); - eth_dispatcher.transfer(bystander(), 100000 * decimals()); - set_contract_address(bystander()); - eth_dispatcher.approve(vault_dispatcher.contract_address, 100000 * decimals()); - // Clear eth transfer events - clear_event_logs(array![eth_dispatcher.contract_address]); - return ( - VaultFacade { vault_dispatcher }, - MarketAggregatorFacade { contract_address: mk_agg.contract_address } + clear_event_logs( + array![ + eth_dispatcher.contract_address, vault_facade.contract_address(), current_round_address + ] ); -} -fn setup_facade() -> (VaultFacade, ERC20ABIDispatcher) { - let (mut vault_facade, _) = setup_facade_vault_type(VaultType::AtTheMoney); - let eth_address = vault_facade.get_eth_address(); - let eth_dispatcher = ERC20ABIDispatcher { contract_address: eth_address }; - (vault_facade, eth_dispatcher) + return vault_facade; } -fn deploy_custom_option_round( - vault_address: ContractAddress, - option_round_id: u256, - auction_start_date: u64, - auction_end_date: u64, - option_settlement_date: u64, - reserve_price: u256, - cap_level: u16, - strike_price: u256 -) -> OptionRoundFacade { - let mut calldata = array![]; - calldata.append_serde(vault_address); - calldata.append_serde(option_round_id); - - calldata.append_serde(auction_start_date); - calldata.append_serde(auction_end_date); - calldata.append_serde(option_settlement_date); - - calldata.append_serde(reserve_price); - calldata.append_serde(cap_level); - calldata.append_serde(strike_price); - - let now = get_block_timestamp(); - let salt = 'something salty' + now.into(); - - let (contract_address, _) = deploy_syscall( - OptionRound::TEST_CLASS_HASH.try_into().unwrap(), salt, calldata.span(), true - ) - .expect('Deploy Custom Round Failed'); - - let vault_dispatcher = IVaultDispatcher { contract_address: vault_address }; - eth_supply_and_approve_all_bidders(contract_address, vault_dispatcher.get_eth_address()); - // Clear the event log - clear_event_logs(array![contract_address]); +fn setup_facade() -> (VaultFacade, ERC20ABIDispatcher) { + let mut vault_facade = setup_facade_vault_type(VaultType::AtTheMoney); + let eth_dispatcher = ERC20ABIDispatcher { contract_address: vault_facade.get_eth_address() }; - OptionRoundFacade { option_round_dispatcher: IOptionRoundDispatcher { contract_address } } + (vault_facade, eth_dispatcher) } fn setup_test_auctioning_bidders( @@ -319,10 +190,11 @@ fn setup_test_auctioning_bidders( fn setup_test_running() -> (VaultFacade, OptionRoundFacade) { let (mut vault, _) = setup_facade(); + let mut current_round = vault.get_current_round(); accelerate_to_auctioning(ref vault); - let mut current_round = vault.get_current_round(); accelerate_to_running(ref vault); + (vault, current_round) } diff --git a/src/tests/utils/lib/structs.cairo b/src/tests/utils/lib/structs.cairo deleted file mode 100644 index 19d815a9..00000000 --- a/src/tests/utils/lib/structs.cairo +++ /dev/null @@ -1,60 +0,0 @@ -use pitch_lake_starknet::{tests::{utils::lib::{variables::{decimals, week_duration}}}}; - -#[derive(Copy, Drop, Serde, starknet::Store, PartialEq)] -struct OptionRoundParams { - // @note Discuss if we should set these when previous round settles, or shortly after when this round's auction starts - current_average_basefee: u256, // average basefee the last few months, used to calculate the strike - standard_deviation: u256, // used to calculate k (-σ or 0 or σ if vault is: ITM | ATM | OTM) - strike_price: u256, // K = current_average_basefee * (1 + k) - cap_level: u256, // cl, percentage points of K that the options will pay out at most. Payout = min(cl*K, BF-K). Might not be set until auction settles if we use alternate cap design (see DOCUMENTATION.md) - collateral_level: u256, // total deposits now locked in the round - reserve_price: u256, // minimum price per option in the auction - total_options_available: u256, - minimum_collateral_required: u256, // the auction will not start unless this much collateral is deposited, needed ? - // @dev should we consider setting this upon auction start ? - // that way if the round's auction start is delayed (due to collateral requirements), we can set a proper auction end time - // when it eventually starts ? - auction_end_time: u64, // when an auction can end - // @dev same as auction end time, wait to set when round acutally starts ? - option_expiry_time: u64, // when the options can be settled -} - -fn mock_option_params() -> OptionRoundParams { - let total_unallocated_liquidity: u256 = 10000 * decimals(); // from LPs ? - let option_reserve_price_: u256 = 6 * decimals(); // from market aggregator (fossil) ? - let average_basefee: u256 = 20; // from market aggregator (fossil) ? - let standard_deviation: u256 = 30; // from market aggregator (fossil) ? - let cap_level: u256 = average_basefee - + (3 - * standard_deviation); //per notes from tomasz, we set cap level at 3 standard deviation (captures 99.7% of the data points) - - let in_the_money_strike_price: u256 = average_basefee + standard_deviation; - //let at_the_money_strike_price: u256 = average_basefee; - //let out_the_money_strike_price: u256 = average_basefee - standard_deviation; - - let collateral_level: u256 = cap_level - in_the_money_strike_price; // per notes from tomasz - let total_options_available: u256 = total_unallocated_liquidity / collateral_level; - - let option_reserve_price: u256 = option_reserve_price_; // just an assumption - - // option_expiry_time:u64, // OptionRound cannot settle before this time - // auction_end_time:u64, // auction cannot settle before this time - // minimum_bid_amount:u256, // to prevent a dos vector - // minimum_collateral_required:u256 // the option round will not start until this much collateral is deposited - - let tmp = OptionRoundParams { - current_average_basefee: average_basefee, - strike_price: in_the_money_strike_price, - standard_deviation: standard_deviation, - cap_level: cap_level, - collateral_level: collateral_level, - reserve_price: option_reserve_price, - total_options_available: total_options_available, - // start_time:timestamp_start_month(), - option_expiry_time: 'todo'.try_into().unwrap(), - auction_end_time: week_duration(), - minimum_collateral_required: 10000, - }; - return tmp; -} - diff --git a/src/tests/vault/liquidity_providers/deposit_tests.cairo b/src/tests/vault/liquidity_providers/deposit_tests.cairo index 6e3eb149..afcf00ee 100644 --- a/src/tests/vault/liquidity_providers/deposit_tests.cairo +++ b/src/tests/vault/liquidity_providers/deposit_tests.cairo @@ -5,10 +5,12 @@ use starknet::{ Felt252TryIntoContractAddress, get_contract_address, get_block_timestamp, testing::{set_block_timestamp, set_contract_address} }; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; -use pitch_lake_starknet::{ - types::{OptionRoundState}, vault::contract::Vault, - option_round::{interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,}}, +use openzeppelin_token::erc20::interface::{ERC20ABIDispatcherTrait,}; +use pitch_lake::{ + vault::contract::Vault, + option_round::{ + interface::{OptionRoundState, IOptionRoundDispatcher, IOptionRoundDispatcherTrait,} + }, library::eth::Eth, tests::{ utils::{ @@ -128,10 +130,12 @@ fn test_depositing_to_vault_eth_transfer() { #[test] #[available_gas(50000000)] -#[should_panic(expected: ('u256_sub Overflow', 'ENTRYPOINT_FAILED', 'ENTRYPOINT_FAILED'))] +#[should_panic( + expected: ('ERC20: insufficient allowance', 'ENTRYPOINT_FAILED', 'ENTRYPOINT_FAILED') +)] fn test_depositing_to_vault_no_approval() { let (mut vault, eth) = setup_facade(); - let mut liquidity_provider = liquidity_provider_1(); + let mut liquidity_provider = contract_address_const::<'fresh user'>(); let mut deposit_amount = 50 * decimals(); set_contract_address(liquidity_provider); @@ -140,7 +144,8 @@ fn test_depositing_to_vault_no_approval() { vault.deposit(deposit_amount, liquidity_provider); } -// Test deposits always go to the vault's unlocked pool, regardless of the state of the current round +// Test deposits always go to the vault's unlocked pool, regardless of the state of the current +// round #[test] #[available_gas(90000000)] fn test_deposits_always_go_to_unlocked_pool() { diff --git a/src/tests/vault/liquidity_providers/withdraw_tests.cairo b/src/tests/vault/liquidity_providers/withdraw_tests.cairo index 92817688..8c14f319 100644 --- a/src/tests/vault/liquidity_providers/withdraw_tests.cairo +++ b/src/tests/vault/liquidity_providers/withdraw_tests.cairo @@ -1,13 +1,13 @@ use core::option::OptionTrait; use core::array::SpanTrait; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; +use openzeppelin_token::erc20::interface::{ERC20ABIDispatcherTrait,}; use starknet::{ ClassHash, ContractAddress, contract_address_const, deploy_syscall, Felt252TryIntoContractAddress, get_contract_address, get_block_timestamp, testing::{set_block_timestamp, set_contract_address} }; -use pitch_lake_starknet::{ - types::Errors, library::eth::Eth, vault::{contract::Vault}, +use pitch_lake::{ + library::eth::Eth, vault::{contract::Vault}, vault::contract::Vault::Errors, tests::{ utils::{ helpers::{ @@ -47,8 +47,8 @@ use debug::PrintTrait; // @note can use the same array of withdraw amounts/lps for each test (0, ...) // Test withdraw 0 does not fail, but balances are unchanged. -//@note confirm if gas changes will also affect the balance, should we only check for vault balance or -//calculate gas amount to correct the balance. +//@note confirm if gas changes will also affect the balance, should we only check for vault balance +//or calculate gas amount to correct the balance. // Test withdrawing > unlocked balance fails #[test] #[available_gas(50000000)] @@ -153,7 +153,8 @@ fn test_withdrawing_from_vault_eth_transfer() { } } -// Test withdrawal always come from the vault's unlocked pool, regardless of the state of the current round +// Test withdrawal always come from the vault's unlocked pool, regardless of the state of the +// current round #[test] #[available_gas(90000000)] fn test_withdrawing_always_come_from_unlocked_pool() { diff --git a/src/tests/vault/liquidity_providers/withdrawal_queue_tests.cairo b/src/tests/vault/liquidity_providers/withdrawal_queue_tests.cairo index a14a8bc2..1ee06aab 100644 --- a/src/tests/vault/liquidity_providers/withdrawal_queue_tests.cairo +++ b/src/tests/vault/liquidity_providers/withdrawal_queue_tests.cairo @@ -1,13 +1,13 @@ use core::option::OptionTrait; use core::array::SpanTrait; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; +use openzeppelin_token::erc20::interface::{ERC20ABIDispatcherTrait,}; use starknet::{ ClassHash, ContractAddress, contract_address_const, deploy_syscall, Felt252TryIntoContractAddress, get_contract_address, get_block_timestamp, testing::{set_block_timestamp, set_contract_address} }; -use pitch_lake_starknet::{ - types::{Errors, Consts::BPS}, library::eth::Eth, vault::{contract::Vault}, +use pitch_lake::{ + types::{Consts::BPS}, library::eth::Eth, vault::{contract::Vault::Errors}, tests::{ utils::{ helpers::{ @@ -233,7 +233,8 @@ fn test_stashed_liquidity_does_not_roll_over() { assert_eq!(lp_stashed2, total_remaining + total_remaining2); } -// Test that when an LP queues a withdrawal, it does not roll over after round settles (multiple LPs) +// Test that when an LP queues a withdrawal, it does not roll over after round settles (multiple +// LPs) #[test] #[available_gas(300000000)] fn test_stashed_liquidity_does_not_roll_over_multiple_LPs() { @@ -391,14 +392,13 @@ fn test_vault_queued_bps_is_accurate() { let total_queued = { let mut total = 0; let mut i = 0; - while i < bps_multi - .len() { - let deposit_amount = *deposit_amounts.at(i); - let bps = *bps_multi.at(i); - total += (deposit_amount * bps.into()) / BPS.into(); - - i += 1; - }; + while i < bps_multi.len() { + let deposit_amount = *deposit_amounts.at(i); + let bps = *bps_multi.at(i); + total += (deposit_amount * bps.into()) / BPS.into(); + + i += 1; + }; total }; let expected_vault_bps = (total_queued * BPS.into()) / current_round.starting_liquidity(); @@ -432,14 +432,13 @@ fn test_vault_queued_bps_changes_correctly() { let total_queued = { let mut total = 0; let mut i = 0; - while i < bps_multi2 - .len() { - let deposit_amount = *deposit_amounts.at(i); - let bps = *bps_multi2.at(i); - total += (deposit_amount * bps.into()) / BPS.into(); - - i += 1; - }; + while i < bps_multi2.len() { + let deposit_amount = *deposit_amounts.at(i); + let bps = *bps_multi2.at(i); + total += (deposit_amount * bps.into()) / BPS.into(); + + i += 1; + }; total }; let expected_vault_bps = (total_queued * BPS.into()) / current_round.starting_liquidity(); @@ -452,14 +451,13 @@ fn test_vault_queued_bps_changes_correctly() { let total_queued = { let mut total = 0; let mut i = 0; - while i < bps_multi3 - .len() { - let deposit_amount = *deposit_amounts.at(i); - let bps = *bps_multi3.at(i); - total += (deposit_amount * bps.into()) / BPS.into(); - - i += 1; - }; + while i < bps_multi3.len() { + let deposit_amount = *deposit_amounts.at(i); + let bps = *bps_multi3.at(i); + total += (deposit_amount * bps.into()) / BPS.into(); + + i += 1; + }; total }; let expected_vault_bps = (total_queued * BPS.into()) / current_round.starting_liquidity(); @@ -628,7 +626,8 @@ fn test_claiming_queued_liquidity_twice_does_nothing() { assert_eq!(stashed_balance_after, stashed_balance_before); } -// Test queuing a withdrawal does not affect the stashed balances while round is Auctioning | Running +// Test queuing a withdrawal does not affect the stashed balances while round is Auctioning | +// Running #[test] #[available_gas(300000000)] fn test_queueing_withdrawal_does_not_affect_stashed_balance_before_round_settle() { @@ -638,59 +637,55 @@ fn test_queueing_withdrawal_does_not_affect_stashed_balance_before_round_settle( let deposit_amount = 100 * decimals(); let deposit_amounts = create_array_linear(deposit_amount, 3).span(); - while rounds_to_run - .is_non_zero() { - let mut round = vault.get_current_round(); - - // Stashed balances while Auctioning before queueing - let stashed_balances_before_auctioning = vault - .get_lp_stashed_balances(liquidity_providers); - accelerate_to_auctioning_custom(ref vault, liquidity_providers, deposit_amounts); - - // Liquidity provider 1 queues withdrawal - vault.queue_withdrawal(*liquidity_providers.at(0), 10_000); - vault.queue_withdrawal(*liquidity_providers.at(0), 10_000); - - // Stashed balances while Auctioning after queueing - let stashed_balances_before_running = vault - .get_lp_stashed_balances(liquidity_providers); - accelerate_to_running(ref vault); - - // Liquidity provider 1 & 2 queue withdrawal - vault.queue_withdrawal(*liquidity_providers.at(0), 10_000); - vault.queue_withdrawal(*liquidity_providers.at(1), 10_000); - - let stashed_balances_before_settled = vault - .get_lp_stashed_balances(liquidity_providers); - accelerate_to_settled(ref vault, round.get_strike_price()); - - let stashed_balances_after_settled = vault.get_lp_stashed_balances(liquidity_providers); - - // Assert stashed balances are unchanged until auction settled - assert!( - stashed_balances_before_auctioning == stashed_balances_before_running, - "stashed before auctioning != stashed before running" - ); - assert!( - stashed_balances_before_running == stashed_balances_before_settled, - "stashed before running != stashed before settled" - ); - - assert!( - stashed_balances_before_settled.at(0) != stashed_balances_after_settled.at(0), - "stashed before settled 1 == stashed after settled 1" - ); - assert!( - stashed_balances_before_settled.at(1) != stashed_balances_after_settled.at(1), - "stashed before settled 2 == stashed after settled 2" - ); - assert!( - stashed_balances_before_settled.at(2) == stashed_balances_after_settled.at(2), - "stashed before settled 3 != stashed after settled 3" - ); - - rounds_to_run -= 1; - } + while rounds_to_run.is_non_zero() { + let mut round = vault.get_current_round(); + + // Stashed balances while Auctioning before queueing + let stashed_balances_before_auctioning = vault.get_lp_stashed_balances(liquidity_providers); + accelerate_to_auctioning_custom(ref vault, liquidity_providers, deposit_amounts); + + // Liquidity provider 1 queues withdrawal + vault.queue_withdrawal(*liquidity_providers.at(0), 10_000); + vault.queue_withdrawal(*liquidity_providers.at(0), 10_000); + + // Stashed balances while Auctioning after queueing + let stashed_balances_before_running = vault.get_lp_stashed_balances(liquidity_providers); + accelerate_to_running(ref vault); + + // Liquidity provider 1 & 2 queue withdrawal + vault.queue_withdrawal(*liquidity_providers.at(0), 10_000); + vault.queue_withdrawal(*liquidity_providers.at(1), 10_000); + + let stashed_balances_before_settled = vault.get_lp_stashed_balances(liquidity_providers); + accelerate_to_settled(ref vault, round.get_strike_price()); + + let stashed_balances_after_settled = vault.get_lp_stashed_balances(liquidity_providers); + + // Assert stashed balances are unchanged until auction settled + assert!( + stashed_balances_before_auctioning == stashed_balances_before_running, + "stashed before auctioning != stashed before running" + ); + assert!( + stashed_balances_before_running == stashed_balances_before_settled, + "stashed before running != stashed before settled" + ); + + assert!( + stashed_balances_before_settled.at(0) != stashed_balances_after_settled.at(0), + "stashed before settled 1 == stashed after settled 1" + ); + assert!( + stashed_balances_before_settled.at(1) != stashed_balances_after_settled.at(1), + "stashed before settled 2 == stashed after settled 2" + ); + assert!( + stashed_balances_before_settled.at(2) == stashed_balances_after_settled.at(2), + "stashed before settled 3 != stashed after settled 3" + ); + + rounds_to_run -= 1; + } } @@ -803,7 +798,7 @@ fn test_stashed_balance_correct_after_round_settles() { ); i += 1; }; -// assert_eq!(stashed_balances_after_settled2, expected_stashed_amounts2,); + // assert_eq!(stashed_balances_after_settled2, expected_stashed_amounts2,); // assert_eq!(unlocked_balances_after_settled2, expected_unlocked_amounts2,); } diff --git a/src/tests/vault/state_transition/auction_end_tests.cairo b/src/tests/vault/state_transition/auction_end_tests.cairo index 5569fe5b..cd3c407e 100644 --- a/src/tests/vault/state_transition/auction_end_tests.cairo +++ b/src/tests/vault/state_transition/auction_end_tests.cairo @@ -4,16 +4,19 @@ use starknet::{ Felt252TryIntoContractAddress, get_contract_address, get_block_timestamp, testing::{set_block_timestamp, set_contract_address} }; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; -use pitch_lake_starknet::{ - library::eth::Eth, types::{OptionRoundState, Errors}, +use openzeppelin_token::erc20::interface::{ERC20ABIDispatcherTrait,}; +use pitch_lake::{ + library::eth::Eth, vault::{ contract::Vault, interface::{ IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, IVaultSafeDispatcherTrait }, }, - option_round::{interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,},}, + option_round::{ + interface::{OptionRoundState, IOptionRoundDispatcher, IOptionRoundDispatcherTrait,}, + }, + option_round::contract::OptionRound::Errors, tests::{ utils::{ helpers::{ diff --git a/src/tests/vault/state_transition/auction_start_tests.cairo b/src/tests/vault/state_transition/auction_start_tests.cairo index 6240a9e4..183bae3c 100644 --- a/src/tests/vault/state_transition/auction_start_tests.cairo +++ b/src/tests/vault/state_transition/auction_start_tests.cairo @@ -3,16 +3,18 @@ use starknet::{ Felt252TryIntoContractAddress, get_contract_address, get_block_timestamp, testing::{set_block_timestamp, set_contract_address} }; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcherTrait,}; -use pitch_lake_starknet::{ - types::{OptionRoundState, Errors}, library::eth::Eth, +use openzeppelin_token::erc20::interface::{ERC20ABIDispatcherTrait,}; +use pitch_lake::{ vault::{ contract::Vault, interface::{ IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, IVaultSafeDispatcherTrait } }, - option_round::{interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,},}, + option_round::{ + interface::{OptionRoundState, IOptionRoundDispatcher, IOptionRoundDispatcherTrait} + }, + option_round::contract::OptionRound::Errors, tests::{ utils::{ helpers::{ @@ -239,19 +241,22 @@ fn test_starting_auction_updates_locked_and_unlocked_balances() { //fn test_start_auction_updates_vault_and_lp_spreads_complex() { // let (mut vault, _) = setup_facade(); // let mut liquidity_providers = liquidity_providers_get(4).span(); -// let mut deposit_amounts = create_array_gradient(100 * decimals(), 100 * decimals(), liquidity_providers.len()) +// let mut deposit_amounts = create_array_gradient(100 * decimals(), 100 * decimals(), +// liquidity_providers.len()) // .span(); // (100, 200, 300, 400) // let total_deposits = sum_u256_array(deposit_amounts); // // // Vault and liquidity providers' balances before auction starts // let mut liquidity_providers_locked_before = vault.get_lp_locked_balances(liquidity_providers); -// let mut liquidity_providers_unlocked_before = vault.deposit_multiple(deposit_amounts, liquidity_providers); +// let mut liquidity_providers_unlocked_before = vault.deposit_multiple(deposit_amounts, +// liquidity_providers); // let (vault_locked_before, vault_unlocked_before) = vault.get_balance_spread(); // // Start auction // vault.start_auction(); // // Vault and liquidity providers' balances after auction starts // let mut liquidity_providers_locked_after = vault.get_lp_locked_balances(liquidity_providers); -// let mut liquidity_providers_unlocked_after = vault.get_lp_unlocked_balances(liquidity_providers); +// let mut liquidity_providers_unlocked_after = +// vault.get_lp_unlocked_balances(liquidity_providers); // let (vault_locked_after, vault_unlocked_after) = vault.get_balance_spread(); // // // Check vault balance @@ -290,7 +295,8 @@ fn test_starting_auction_updates_locked_and_unlocked_balances() { // // let (mut vault, _) = setup_facade(); // let mut liquidity_providers = liquidity_providers_get(4).span(); -// let mut round1_deposits = create_array_gradient(100 * decimals(), 100 * decimals(), liquidity_providers.len()) +// let mut round1_deposits = create_array_gradient(100 * decimals(), 100 * decimals(), +// liquidity_providers.len()) // .span(); // (100, 200, 300, 400) // let starting_liquidity1 = sum_u256_array(round1_deposits); // accelerate_to_auctioning_custom(ref vault, liquidity_providers, round1_deposits); @@ -339,7 +345,8 @@ fn test_starting_auction_updates_locked_and_unlocked_balances() { // Option::Some(lp_spread_before) => { // let lp_spread_after = lp_spreads_after.pop_front().unwrap(); // let lp_starting_liquidity2 = round2_deposits.pop_front().unwrap(); -// assert(lp_spread_before == (0, *lp_starting_liquidity2), 'LP spread before wrong'); +// assert(lp_spread_before == (0, *lp_starting_liquidity2), 'LP spread before +// wrong'); // assert(lp_spread_after == (*lp_starting_liquidity2, 0), 'LP spread after wrong'); // }, // Option::None => { break (); } @@ -355,13 +362,16 @@ fn test_starting_auction_updates_locked_and_unlocked_balances() { // let (mut vault, _) = setup_facade(); // // Accelerate the round to running // let liquidity_providers = liquidity_providers_get(5); -// let mut deposit_amounts = create_array_gradient(100 * decimals(), 100 * decimals(), liquidity_providers.len()); // (100, 200, 300...500) -// accelerate_to_auctioning_custom(ref vault, liquidity_providers.span(), deposit_amounts.span()); +// let mut deposit_amounts = create_array_gradient(100 * decimals(), 100 * decimals(), +// liquidity_providers.len()); // (100, 200, 300...500) +// accelerate_to_auctioning_custom(ref vault, liquidity_providers.span(), +// deposit_amounts.span()); // accelerate_to_running(ref vault); // let mut current_round = vault.get_current_round(); // // // Spreads for the vault and each lp (locked, unlocked) before option round settles -// let mut all_lp_spreads_before: Array<(u256, u256)> = vault.deposit_multiple(deposit_amounts.span(), liquidity_providers.span()); +// let mut all_lp_spreads_before: Array<(u256, u256)> = +// vault.deposit_multiple(deposit_amounts.span(), liquidity_providers.span()); // let (vault_locked_init, vault_unlocked) = vault.get_balance_spread(); // // Settle option round (with no payout) // accelerate_to_settled(ref vault, current_round.get_strike_price(), ); @@ -379,7 +389,8 @@ fn test_starting_auction_updates_locked_and_unlocked_balances() { // Option::Some(( // locked_amount, unlocked_amount // )) => { -// assert(locked_amount == deposit_amounts.pop_front().unwrap(), 'Locked balance mismatch'); +// assert(locked_amount == deposit_amounts.pop_front().unwrap(), 'Locked balance +// mismatch'); // assert(unlocked_amount == 0, 'Unlocked balance mismatch'); // }, // Option::None => { break (); } @@ -398,8 +409,8 @@ fn test_starting_auction_updates_locked_and_unlocked_balances() { //fn test_start_auction_under_minium_collateral_required_failure() { // let (mut vault_facade, _) = setup_facade(); // -// // @dev Need to manually initialize round 1 unless it is initialed during the vault constructor -// // ... vault::_initialize_round_1() +// // @dev Need to manually initialize round 1 unless it is initialed during the vault +// constructor // ... vault::_initialize_round_1() // // // Get round 1's minium collateral requirements // let mut next_round: OptionRoundFacade = vault_facade.get_next_round(); diff --git a/src/tests/vault/state_transition/option_settle_tests.cairo b/src/tests/vault/state_transition/option_settle_tests.cairo index d9710c7b..09fdb7d9 100644 --- a/src/tests/vault/state_transition/option_settle_tests.cairo +++ b/src/tests/vault/state_transition/option_settle_tests.cairo @@ -3,22 +3,18 @@ use starknet::{ Felt252TryIntoContractAddress, get_contract_address, get_block_timestamp, testing::{set_block_timestamp, set_contract_address}, contract_address::ContractAddressZeroable, }; -use openzeppelin::{ - utils::serde::SerializedAppend, token::erc20::interface::{ERC20ABIDispatcherTrait} -}; -use pitch_lake_starknet::{ - types::Errors, library::eth::Eth, +use openzeppelin_utils::serde::SerializedAppend; +use openzeppelin_token::erc20::interface::ERC20ABIDispatcherTrait; +use pitch_lake::{ + library::{eth::Eth, constants::{ROUND_TRANSITION_PERIOD, AUCTION_RUN_TIME, OPTION_RUN_TIME}}, vault::{ contract::Vault, interface::{ - IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, IVaultSafeDispatcherTrait + IVaultDispatcher, IVaultSafeDispatcher, IVaultDispatcherTrait, + IVaultSafeDispatcherTrait, VaultType, } }, - option_round::{interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait,}}, - market_aggregator::interface::{ - IMarketAggregator, IMarketAggregatorDispatcher, IMarketAggregatorDispatcherTrait, - IMarketAggregatorSafeDispatcher, IMarketAggregatorSafeDispatcherTrait - }, + option_round::contract::OptionRound::Errors, option_round::interface::PricingData, tests::{ utils::{ helpers::{ @@ -28,13 +24,17 @@ use pitch_lake_starknet::{ }, event_helpers::{ clear_event_logs, assert_event_option_settle, assert_event_transfer, - assert_no_events_left, pop_log, assert_event_option_round_deployed, + assert_no_events_left, pop_log, assert_event_option_round_deployed_single, + assert_event_option_round_deployed, }, accelerators::{ accelerate_to_auctioning, accelerate_to_running, accelerate_to_settled, accelerate_to_auctioning_custom }, - setup::{setup_facade, setup_test_auctioning_providers, setup_test_running}, + setup::{ + deploy_vault_with_events, setup_facade, setup_test_auctioning_providers, + setup_test_running + }, }, lib::{ test_accounts::{ @@ -46,9 +46,7 @@ use pitch_lake_starknet::{ }, facades::{ vault_facade::{VaultFacade, VaultFacadeTrait}, - option_round_facade::{ - OptionRoundParams, OptionRoundState, OptionRoundFacade, OptionRoundFacadeTrait - }, + option_round_facade::{OptionRoundState, OptionRoundFacade, OptionRoundFacadeTrait}, }, }, } @@ -63,6 +61,7 @@ use debug::PrintTrait; #[available_gas(50000000)] fn test_settling_option_round_while_round_auctioning_fails() { let (mut vault_facade, _) = setup_facade(); + accelerate_to_auctioning(ref vault_facade); // Settle option round before auction ends @@ -118,52 +117,89 @@ fn test_option_round_settled_event() { } } -// Test every time a new round is deployed, the next round deployed event emits correctly -// @dev The first round to be deployed after deployment is round 2 +fn get_expected_dates(ref vaul: VaultFacade, deployment_date: u64) -> (u64, u64, u64) { + let auction_start_date = deployment_date + ROUND_TRANSITION_PERIOD; + let auction_end_date = auction_start_date + AUCTION_RUN_TIME; + let option_settlement_date = auction_end_date + OPTION_RUN_TIME; + + (auction_start_date, auction_end_date, option_settlement_date) +} + #[test] #[available_gas(500000000)] -fn test_next_round_deployed_event() { - let mut rounds_to_run = 3; - let (mut vault, _) = setup_facade(); - - while rounds_to_run > 0_u32 { - let mut current_round = vault.get_current_round(); - let current_round_id = vault.get_current_round_id(); - accelerate_to_auctioning(ref vault); - accelerate_to_running(ref vault); +fn test_first_round_deployed_event() { + let vault_dispatcher = deploy_vault_with_events( + VaultType::AtTheMoney, contract_address_const::<'eth'>() + ); + let mut vault = VaultFacade { vault_dispatcher }; + let mut current_round = vault.get_current_round(); - clear_event_logs(array![vault.contract_address()]); - accelerate_to_settled(ref vault, current_round.get_strike_price()); + let (auction_start_date, auction_end_date, option_settlement_date) = get_expected_dates( + ref vault, current_round.get_deployment_date() + ); - let mut new_current_round = vault.get_current_round(); - let reserve_price = new_current_round.get_reserve_price(); - let strike_price = new_current_round.get_strike_price(); - let cap_level = new_current_round.get_cap_level(); - let auction_start_date = new_current_round.get_auction_start_date(); - let auction_end_date = new_current_round.get_auction_end_date(); - let settlement_date = new_current_round.get_option_settlement_date(); + assert_eq!(current_round.get_deployment_date(), get_block_timestamp()); + assert( + auction_start_date.is_non_zero() + && auction_end_date.is_non_zero() + && option_settlement_date.is_non_zero(), + 'Dates should not be 0' + ); + // @note doing this to add an extra vault event (so that the double pop log works as expected) + //vault.withdraw(0, liquidity_provider_1()); + assert_event_option_round_deployed_single( + vault.contract_address(), + 1, + current_round.contract_address(), + auction_start_date, + auction_end_date, + option_settlement_date, + pricing_data: PricingData { strike_price: 0, cap_level: 0, reserve_price: 0 } + ); +} - // Check new round is deployed - assert( - current_round.get_round_id() + 1 == new_current_round.get_round_id(), - 'round contract address wrong' - ); - // Check the event emits correctly - assert_event_option_round_deployed( - vault - .contract_address(), // @dev round 2 should be the first round to deploy post deployment - current_round_id + 1, - new_current_round.contract_address(), - reserve_price, - strike_price, - cap_level, - auction_start_date, - auction_end_date, - settlement_date - ); +// Test every time a new round is deployed, the next round deployed event emits correctly +// @dev The first round to be deployed after deployment is round 2 +#[test] +#[available_gas(500000000)] +fn test_next_round_deployed_events() { + let (mut vault, _) = setup_facade(); - rounds_to_run -= 1; - } + for i in 1_u256 + ..4 { + let mut round_i = vault.get_current_round(); + accelerate_to_auctioning(ref vault); + accelerate_to_running(ref vault); + + clear_event_logs(array![vault.contract_address()]); + accelerate_to_settled(ref vault, round_i.get_strike_price()); + + let mut round_i_plus_1 = vault.get_current_round(); + let auction_start_date = round_i_plus_1.get_auction_start_date(); + let auction_end_date = round_i_plus_1.get_auction_end_date(); + let settlement_date = round_i_plus_1.get_option_settlement_date(); + + // Check new round is deployed + assert(i + 1 == round_i_plus_1.get_round_id(), 'round contract address wrong'); + + // Check the event emits correctly + let pricing_data = PricingData { + strike_price: round_i_plus_1.get_strike_price(), + cap_level: round_i_plus_1.get_cap_level(), + reserve_price: round_i_plus_1.get_reserve_price() + }; + + assert(pricing_data != Default::default(), 'Pricing data not set correctly'); + assert_event_option_round_deployed( + vault.contract_address(), + i + 1, + round_i_plus_1.contract_address(), + auction_start_date, + auction_end_date, + settlement_date, + pricing_data + ); + } } @@ -347,7 +383,8 @@ fn test_settling_option_round_updates_locked_and_unlocked_balances() { // // Accelerate through round 1 with premiums and a payout // let (mut vault, _) = setup_facade(); // let mut liquidity_providers = liquidity_providers_get(4).span(); -// let round1_deposits = create_array_gradient(100 * decimals(), 100 * decimals(), liquidity_providers.len()) +// let round1_deposits = create_array_gradient(100 * decimals(), 100 * decimals(), +// liquidity_providers.len()) // .span(); // (100, 200, 300, 400) // let starting_liquidity1 = sum_u256_array(round1_deposits); // accelerate_to_auctioning_custom(ref vault, liquidity_providers, round1_deposits); @@ -413,12 +450,14 @@ fn test_settling_option_round_updates_locked_and_unlocked_balances() { // let lp_spread_after = lp_spreads_after.pop_front().unwrap(); // let lp_starting_liquidity2 = round2_deposits.pop_front().unwrap(); // let lp_premiums2 = individual_premiums2.pop_front().unwrap(); -// let lp_remaining_liquidity2 = individual_remaining_liquidity2.pop_front().unwrap(); +// let lp_remaining_liquidity2 = +// individual_remaining_liquidity2.pop_front().unwrap(); // assert( // *lp_spread_before == (*lp_starting_liquidity2, *lp_premiums2), // 'LP spread before wrong' // ); -// assert(*lp_spread_after == (0, *lp_remaining_liquidity2), 'LP spread after wrong'); +// assert(*lp_spread_after == (0, *lp_remaining_liquidity2), 'LP spread after +// wrong'); // }, // Option::None => { break (); } // } diff --git a/src/types.cairo b/src/types.cairo index 1b266a9d..b6352fed 100644 --- a/src/types.cairo +++ b/src/types.cairo @@ -1,86 +1,20 @@ 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'; - const QueueingMoreThanPositionValue: felt252 = 'Insufficient balance to queue'; - const WithdrawalQueuedWhileUnlocked: felt252 = 'Can only queue while locked'; - /// OptionRound Errors /// - const CallerIsNotVault: felt252 = 'Caller not the Vault'; - // Starting an auction - const AuctionStartDateNotReached: felt252 = 'Auction start date not reached'; - const AuctionAlreadyStarted: felt252 = 'Auction already started'; - // Ending an auction - const AuctionEndDateNotReached: felt252 = 'Auction end date not reached'; - const AuctionAlreadyEnded: felt252 = 'Auction has already ended'; - // Settling an option round - const OptionSettlementDateNotReached: felt252 = 'Settlement date not reached'; - const OptionRoundAlreadySettled: felt252 = 'Option round already settled'; - // Bidding & upating bids - const BiddingWhileNotAuctioning: felt252 = 'Can only bid while auctioning'; - const BidAmountZero: felt252 = 'Bid amount cannot be 0'; - const BidBelowReservePrice: felt252 = 'Bid price below reserve price'; - const CallerNotBidOwner: felt252 = 'Caller is not bid owner'; - const BidMustBeIncreased: felt252 = 'Bid updates must increase price'; - // Refunding bids & tokenizing options - const AuctionNotEnded: felt252 = 'Auction has not ended yet'; - const OptionRoundNotSettled: felt252 = 'Option round not settled yet'; - /// Internal Errors /// - const OptionRoundDeploymentFailed: felt252 = 'Option round deployment failed'; - const BidsShouldNotHaveSameTreeNonce: felt252 = 'Tree nonces should be unique'; +/// Errors +pub mod Errors { + pub const BidsShouldNotHaveSameTreeNonce: felt252 = 'Tree nonces should be unique'; } -mod Consts { - const BPS: u256 = 10_000; +pub mod Consts { + pub const BPS: u256 = 10_000; + pub const JOB_TIMESTAMP_TOLERANCE: u64 = 1800; // 30 minutes } -/// 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 a Vault to start a round's auction -//#[derive(Copy, Drop, Serde, starknet::Store, PartialEq)] -//struct StartAuctionParams { -// starting_liquidity: 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 { +#[derive(Copy, Drop, Serde, starknet::Store, PartialEq)] +pub struct Bid { bid_id: felt252, owner: ContractAddress, amount: u256, @@ -89,9 +23,10 @@ struct Bid { } // 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 { +// 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 +pub impl BidPartialOrdTrait of PartialOrd { /// < fn lt(lhs: Bid, rhs: Bid) -> bool { if lhs.price < rhs.price { @@ -127,34 +62,34 @@ impl BidPartialOrdTrait of PartialOrd { (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 str: ByteArray = format!( - "Bid ID:{}\nOwner:{}\nAmount:{}\nPrice:{}\nTree Nonce:{}", - *self.bid_id, - Into::::into(*self.owner), - *self.amount, - *self.price, - *self.tree_nonce, - ); - f.buffer.append(@str); - Result::Ok(()) - } -} +//pub impl BidDisplay of Display { +// fn fmt(self: @Bid, ref f: Formatter) -> Result<(), Error> { +// let str: ByteArray = format!( +// "Bid ID:{}\nOwner:{}\nAmount:{}\nPrice:{}\nTree Nonce:{}", +// *self.bid_id, +// Into::::into(*self.owner), +// *self.amount, +// *self.price, +// *self.tree_nonce, +// ); +// f.buffer.append(@str); +// Result::Ok(()) +// } +//} // 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 { - OptionRoundState::Open => { format!("Open") }, - OptionRoundState::Auctioning => { format!("Auctioning") }, - OptionRoundState::Running => { format!("Running") }, - OptionRoundState::Settled => { format!("Settled") } - }; - f.buffer.append(@str); - Result::Ok(()) - } -} +//pub impl OptionRoundStateDisplay of Display { +// fn fmt(self: @OptionRoundState, ref f: Formatter) -> Result<(), Error> { +// let str: ByteArray = match self { +// OptionRoundState::Open => { format!("Open") }, +// OptionRoundState::Auctioning => { format!("Auctioning") }, +// OptionRoundState::Running => { format!("Running") }, +// OptionRoundState::Settled => { format!("Settled") } +// }; +// f.buffer.append(@str); +// Result::Ok(()) +// } +//} + + diff --git a/src/vault/contract.cairo b/src/vault/contract.cairo index 2dc0f3fd..566a44fa 100644 --- a/src/vault/contract.cairo +++ b/src/vault/contract.cairo @@ -1,107 +1,127 @@ #[starknet::contract] mod Vault { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess,}; + use starknet::storage::{Map, StoragePathEntry}; use starknet::{ ContractAddress, ClassHash, deploy_syscall, get_caller_address, contract_address_const, get_contract_address, get_block_timestamp }; - use openzeppelin::{ - token::erc20::{ERC20Component, interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait,}}, - utils::serde::SerializedAppend + use openzeppelin_token::erc20::{ + ERC20Component, interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait} }; - use pitch_lake_starknet::{ - vault::interface::IVault, - option_round::{ - contract::OptionRound, interface::{IOptionRoundDispatcher, IOptionRoundDispatcherTrait}, - }, - market_aggregator::interface::{ - IMarketAggregatorDispatcher, IMarketAggregatorDispatcherTrait - }, - types::{VaultType, OptionRoundState, Errors, Consts::BPS}, + use openzeppelin_utils::serde::SerializedAppend; + use pitch_lake::vault::interface::{ + ConstructorArgs, IVault, VaultType, L1DataRequest, L1Result, L1Data, + }; + use pitch_lake::option_round::contract::{OptionRound, OptionRound::Errors as RoundErrors}; + use pitch_lake::option_round::interface::{ + ConstructorArgs as OptionRoundConstructorArgs, OptionRoundState, IOptionRoundDispatcher, + IOptionRoundDispatcherTrait, PricingData, + }; + use pitch_lake::types::{Consts::{BPS, JOB_TIMESTAMP_TOLERANCE}}; + use pitch_lake::library::utils::{assert_equal_in_range, generate_request_id,}; + use pitch_lake::library::pricing_utils::{calculate_strike_price, calculate_cap_level}; + use pitch_lake::library::constants::{ + MINUTE, HOUR, DAY, //ROUND_TRANSITION_PERIOD, AUCTION_RUN_TIME, OPTION_RUN_TIME }; - use pitch_lake_starknet::library::utils::{calculate_strike_price}; - const TWAP_DURATION: u64 = 60 * 60 * 24 * 14; // 2 weeks + // ************************************************************************* + // Constants + // ************************************************************************* + + const PITCH_LAKE_V1: felt252 = 'PITCH_LAKE_V1'; + const TIMESTAMP_TOLERANCE: u64 = 1 * HOUR; // ************************************************************************* // STORAGE // ************************************************************************* - // Note: Write description of any storage variable here-> - // @eth_address: Address for eth contract - // @option_round_class_hash: Hash for the latest implementation of OptionRound class - // @position: The amount liquidity providers deposit into each round: (liquidity_provider, round_id) -> deposit_amount - // @withdraw_checkpoints: Withdraw checkpoints: (liquidity_provider) -> round_id - // @total_unlocked_balance: Total unlocked liquidity - // @total_locked_balance: Total locked liquidity - // @total_stashed_balance: Total stashed liquidity - // @premiums_collected:The amount of premiums a liquidity provider collects from each round: (liquidity_provider, round_id) -> collected_amount - // @current_option_round_id: The id of the current option round - // @round_addresses: Mapping of round id -> round address - // @round_transition_period: Time between settling of current round and starting of next round - // @auction_run_time: running time for the auction + #[storage] struct Storage { /// - market_aggregator_address: ContractAddress, - eth_address: ContractAddress, + vault_type: VaultType, + alpha: u128, + /// + l1_data: Map, option_round_class_hash: ClassHash, - round_addresses: LegacyMap, + fulfillment_whitelist: Map, + eth_address: ContractAddress, + round_addresses: Map, /// - vault_type: VaultType, - round_transition_period: u64, - auction_run_time: u64, - option_run_time: u64, /// + // @note could use usize ? current_round_id: u256, /// - positions: LegacyMap<(ContractAddress, u256), u256>, + // @note could use CA, (usize, u256) ? + positions: Map>, /// vault_locked_balance: u256, vault_unlocked_balance: u256, vault_stashed_balance: u256, /// - position_checkpoints: LegacyMap, - stash_checkpoints: LegacyMap, - is_premium_moved: LegacyMap<(ContractAddress, u256), bool>, + // @note could use CA, usize ? + position_checkpoints: Map, + // @note could use CA, usize ? + stash_checkpoints: Map, + // @note could use CA, (usize, bool) ? + is_premium_moved: Map>, /// - queued_liquidity: LegacyMap<(ContractAddress, u256), u256>, + // @note could use CA, (usize, u256) + queued_liquidity: Map>, } // ************************************************************************* // Constructor // ************************************************************************* + #[constructor] - fn constructor( - ref self: ContractState, - round_transition_period: u64, - auction_run_time: u64, - option_run_time: u64, - eth_address: ContractAddress, - vault_type: VaultType, - market_aggregator_address: ContractAddress, - option_round_class_hash: ClassHash, - ) { + fn constructor(ref self: ContractState, args: ConstructorArgs) { + // @dev Get the constructor arguments + let ConstructorArgs { request_fulfiller, + eth_address, + vault_type, + option_round_class_hash } = + args; + + // @dev Set the Vault's parameters + self.fulfillment_whitelist.entry(request_fulfiller).write(true); self.eth_address.write(eth_address); self.vault_type.write(vault_type); - self.market_aggregator_address.write(market_aggregator_address); self.option_round_class_hash.write(option_round_class_hash); - self.round_transition_period.write(round_transition_period); - self.auction_run_time.write(auction_run_time); - self.option_run_time.write(option_run_time); - // @dev Deploy the 1st option round - self.deploy_first_round(); + + // @dev Deploy the first round + self.deploy_next_round(Default::default()); + } + + // ************************************************************************* + // Errors + // ************************************************************************* + + mod Errors { + // Fossil + const CallerNotWhitelisted: felt252 = 'Caller not whitelisted'; + const L1DataOutOfRange: felt252 = 'L1 data out of range'; + // Withdraw/queuing withdrawals + const InsufficientBalance: felt252 = 'Insufficient unlocked balance'; + const QueueingMoreThanPositionValue: felt252 = 'Insufficient balance to queue'; + const WithdrawalQueuedWhileUnlocked: felt252 = 'Can only queue while locked'; + // Deploying option rounds + const OptionRoundDeploymentFailed: felt252 = 'Option round deployment failed'; } // ************************************************************************* // EVENTS // ************************************************************************* + #[event] - #[derive(PartialEq, Drop, starknet::Event)] + #[derive(Serde, PartialEq, Drop, starknet::Event)] enum Event { Deposit: Deposit, Withdrawal: Withdrawal, WithdrawalQueued: WithdrawalQueued, StashWithdrawn: StashWithdrawn, OptionRoundDeployed: OptionRoundDeployed, + L1RequestFulfilled: L1RequestFulfilled, } // @dev Emitted when a deposit is made for an account @@ -109,7 +129,7 @@ mod Vault { // @member amount: The amount deposited // @member: account_unlocked_balance_now: The account's unlocked balance after the deposit // @member: vault_unlocked_balance_now: The vault's unlocked balance after the deposit - #[derive(Drop, starknet::Event, PartialEq)] + #[derive(Serde, Drop, starknet::Event, PartialEq)] struct Deposit { #[key] account: ContractAddress, @@ -123,7 +143,7 @@ mod Vault { // @member amount: The amount withdrawn // @member account_unlocked_balance_now: The account's unlocked balance after the withdrawal // @member vault_unlocked_balance_now: The vault's unlocked balance after the withdrawal - #[derive(Drop, starknet::Event, PartialEq)] + #[derive(Serde, Drop, starknet::Event, PartialEq)] struct Withdrawal { #[key] account: ContractAddress, @@ -135,9 +155,10 @@ mod Vault { // @dev Emitted when an account queues a withdrawal // @member account: The account that queued the withdrawal // @member bps: The BPS % of the account's remaining liquidity to stash - // @member account_queued_liquidity_now: The account's starting liquidity queued after the withdrawal - // @member vault_queued_liquidity_now: The vault's starting liquidity queued after the withdrawal - #[derive(Drop, starknet::Event, PartialEq)] + // @member account_queued_liquidity_now: The account's starting liquidity queued after the + // withdrawal @member vault_queued_liquidity_now: The vault's starting liquidity queued after + // the withdrawal + #[derive(Serde, Drop, starknet::Event, PartialEq)] struct WithdrawalQueued { #[key] account: ContractAddress, @@ -150,7 +171,7 @@ mod Vault { // @member account: The account that withdrew the stashed liquidity // @member amount: The amount withdrawn // @member vault_stashed_balance_now: The vault's stashed balance after the withdrawal - #[derive(Drop, starknet::Event, PartialEq)] + #[derive(Serde, Drop, starknet::Event, PartialEq)] struct StashWithdrawn { #[key] account: ContractAddress, @@ -167,21 +188,28 @@ mod Vault { // @member auction_start_date: The auction start date for the deployed round // @member auction_end_date: The auction end date for the deployed round // @member option_settlement_date: The option settlement date for the deployed round - #[derive(Drop, starknet::Event, PartialEq)] + #[derive(Serde, Drop, starknet::Event, PartialEq)] struct OptionRoundDeployed { round_id: u256, address: ContractAddress, - reserve_price: u256, - strike_price: u256, - cap_level: u128, auction_start_date: u64, auction_end_date: u64, option_settlement_date: u64, + pricing_data: PricingData, + } + + #[derive(Serde, Drop, starknet::Event, PartialEq)] + struct L1RequestFulfilled { + #[key] + id: felt252, + #[key] + caller: ContractAddress, } // ************************************************************************* // IMPLEMENTATION // ************************************************************************* + #[abi(embed_v0)] impl VaultImpl of IVault { // *********************************** @@ -194,26 +222,10 @@ mod Vault { self.vault_type.read() } - fn get_market_aggregator_address(self: @ContractState) -> ContractAddress { - self.market_aggregator_address.read() - } - fn get_eth_address(self: @ContractState) -> ContractAddress { self.eth_address.read() } - fn get_auction_run_time(self: @ContractState) -> u64 { - self.auction_run_time.read() - } - - fn get_option_run_time(self: @ContractState) -> u64 { - self.option_run_time.read() - } - - fn get_round_transition_period(self: @ContractState) -> u64 { - self.round_transition_period.read() - } - fn get_current_round_id(self: @ContractState) -> u256 { self.current_round_id.read() } @@ -247,10 +259,12 @@ mod Vault { let total_liq = self .get_round_dispatcher(self.current_round_id.read()) .get_starting_liquidity(); - // @dev Get the amount queued + // @dev Get the vault's queued for the current round let queued_liq = self .queued_liquidity - .read((get_contract_address(), self.current_round_id.read())); + .entry(get_contract_address()) + .entry(self.current_round_id.read()) + .read(); // @dev Calculate the queued BPS %, avoiding division by 0 match total_liq.is_zero() { @@ -297,12 +311,12 @@ mod Vault { let mut total = 0; while i < current_round_id { // @dev Get the liquidity the account queued - let queued_liq = self.queued_liquidity.read((account, i)); + let queued_liq = self.queued_liquidity.entry(account).entry(i).read(); if queued_liq.is_non_zero() { // @dev Get the round's starting and remaining liquidity let (starting_liq, remaining_liq, _) = self.get_round_outcome(i); - // @dev Calculate the amount of remaining liquidity that was stashed for the account, - // avoiding division by 0 + // @dev Calculate the amount of remaining liquidity that was stashed for the + // account, avoiding division by 0 if starting_liq.is_non_zero() { let stashed_liq = (remaining_liq * queued_liq) / starting_liq; total += stashed_liq; @@ -320,15 +334,46 @@ mod Vault { let current_round_id = self.current_round_id.read(); let total_liq = self.get_realized_deposit_for_current_round(account); // @dev Get the amount the account queued - let queued_liq = self.queued_liquidity.read((account, current_round_id)); + let queued_liq = self.queued_liquidity.entry(account).entry(current_round_id).read(); - // @dev Calculate the BPS % of the starting liquidity that is queued, avoiding division by 0 + // @dev Calculate the BPS % of the starting liquidity that is queued, avoiding division + // by 0 match total_liq.is_zero() { true => 0, false => self.divide_into_bps(queued_liq, total_liq) } } + /// Fossil + + fn get_request_to_settle_round(self: @ContractState) -> L1DataRequest { + // @dev Get the current round's settlement date + let settlement_date = self + .get_round_dispatcher(self.current_round_id.read()) + .get_option_settlement_date(); + + // @dev Return the earliest request that will allow `settle_round()` to pass once + // finished - A request is valid as long as its timestamp is >= settlement date - + // tolerance and <= settlement date + L1DataRequest { + identifiers: array![PITCH_LAKE_V1].span(), + timestamp: settlement_date - TIMESTAMP_TOLERANCE, + } + } + + fn get_request_to_start_auction(self: @ContractState) -> L1DataRequest { + // @dev Get the current round's deployment date + let deployment_date = self + .get_round_dispatcher(self.current_round_id.read()) + .get_deployment_date(); + + // @dev Return the earliest request that will allow `start_auction()` to pass once + // finished (if refreshing or not set yet) + // - A request is valid as long as its timestamp is >= deployment date and + // <= auction start date + L1DataRequest { identifiers: array![PITCH_LAKE_V1].span(), timestamp: deployment_date, } + } + // *********************************** // WRITES @@ -341,9 +386,17 @@ mod Vault { // @dev Update the account's current and upcoming round deposits self.refresh_position(account); let upcoming_round_id = self.get_upcoming_round_id(); - let upcoming_round_deposit = self.positions.read((account, upcoming_round_id)); + let upcoming_round_deposit = self + .positions + .entry(account) + .entry(upcoming_round_id) + .read(); let account_unlocked_balance_now = upcoming_round_deposit + amount; - self.positions.write((account, upcoming_round_id), account_unlocked_balance_now); + self + .positions + .entry(account) + .entry(upcoming_round_id) + .write(account_unlocked_balance_now); // @dev Transfer the deposit amount from the caller to this contract let eth = self.get_eth_dispatcher(); @@ -376,14 +429,22 @@ mod Vault { let account = get_caller_address(); self.refresh_position(account); let upcoming_round_id = self.get_upcoming_round_id(); - let upcoming_round_deposit = self.positions.read((account, upcoming_round_id)); + let upcoming_round_deposit = self + .positions + .entry(account) + .entry(upcoming_round_id) + .read(); // @dev Check the caller is not withdrawing more than their upcoming round deposit assert(amount <= upcoming_round_deposit, Errors::InsufficientBalance); // @dev Update the account's upcoming round deposit let account_unlocked_balance_now = upcoming_round_deposit - amount; - self.positions.write((account, upcoming_round_id), account_unlocked_balance_now); + self + .positions + .entry(account) + .entry(upcoming_round_id) + .write(account_unlocked_balance_now); // @dev Update the vault's unlocked balance let vault_unlocked_balance_now = self.vault_unlocked_balance.read() - amount; @@ -433,18 +494,30 @@ mod Vault { // @dev Calculate the's starting liquidity for the vault being queued let vault_previously_queued_liquidity = self .queued_liquidity - .read((get_contract_address(), current_round_id)); + .entry(get_contract_address()) + .entry(current_round_id) + .read(); let account_previously_queued_liquidity = self .queued_liquidity - .read((account, current_round_id)); + .entry(account) + .entry(current_round_id) + .read(); let vault_queued_liquidity_now = vault_previously_queued_liquidity - account_previously_queued_liquidity + account_queued_liquidity_now; // @dev Update the vault and account's queued liquidity let vault = get_contract_address(); - self.queued_liquidity.write((vault, current_round_id), vault_queued_liquidity_now); - self.queued_liquidity.write((account, current_round_id), account_queued_liquidity_now); + self + .queued_liquidity + .entry(vault) + .entry(current_round_id) + .write(vault_queued_liquidity_now); + self + .queued_liquidity + .entry(account) + .entry(current_round_id) + .write(account_queued_liquidity_now); // @dev Emit withdrawal queued event self @@ -485,6 +558,155 @@ mod Vault { /// State transitions + fn fulfill_request(ref self: ContractState, request: L1DataRequest, result: L1Result) { + // @dev Requests can only be fulfilled if the current round is Open || Running + let current_round = self.get_round_dispatcher(self.current_round_id.read()); + let state = current_round.get_state(); + assert( + state == OptionRoundState::Open || state == OptionRoundState::Running, + Errors::L1DataOutOfRange + ); + + // @dev If the current round is Open, the result is being used to refresh the current + // round's pricing data; therefore, its timestamp must be between the round's deployment + // date and auction start date + let mut upper_bound = 0; + let mut lower_bound = 0; + if state == OptionRoundState::Open { + upper_bound = current_round.get_auction_start_date(); + lower_bound = current_round.get_deployment_date(); + } // @dev If the current_round is Running, the result is being used to set the pricing + // data to settle the current round and deploy the next; therefore, its timestamp must + // be on or before the settlement date with some tolerance + else { + upper_bound = current_round.get_option_settlement_date(); + lower_bound = upper_bound - TIMESTAMP_TOLERANCE; + } + + // @dev Ensure result is in bounds + let timestamp = request.timestamp; + assert(timestamp >= lower_bound || timestamp <= upper_bound, Errors::L1DataOutOfRange); + + // @dev Since we are skipping the proof verification, Pitch Lake will use a whitelisted + // address to fulfill requests, eventually update to proof verification instead + let caller = get_caller_address(); + assert(self.fulfillment_whitelist.entry(caller).read(), Errors::CallerNotWhitelisted); + + // @dev If the current round is Open, set its pricing data directly + if state == OptionRoundState::Open { + current_round.set_pricing_data(self.l1_data_to_round_data(result.data)); + } // @dev If the current round is Running, set pricing data to use upon settlement + else { + // @dev Set l1 pricing data for upcoming round settlement + self.l1_data.entry(current_round.get_round_id()).write(result.data); + } + + // @dev Emit request fulfilled event + self + .emit( + Event::L1RequestFulfilled( + L1RequestFulfilled { id: generate_request_id(request), caller } + ) + ); + } + + // fn fulfill_request_to_settle_round( + // ref self: ContractState, request: L1DataRequest, result: L1Result + // ) -> bool { + // // @dev Get the request's ID and caller fulfilling the request + // let id = generate_request_id(request); + /// let caller = get_caller_address(); + // + // // @dev Pricing data can only be set if the current round is Running + // let current_round = self.get_round_dispatcher(self.current_round_id.read()); + // if current_round.get_state() != OptionRoundState::Running { + // // @dev Emit L1 request not fulfilled event + // self + // .emit( + // Event::L1RequestNotFulfilled( + // L1RequestNotFulfilled { id, caller, reason: 'REPLACE ME' } + // ) + // ); + // return false; + // } + // + // // @dev A requst is valid to settle a round if it is the between some + // tolerance and the // settlement date + // let timestamp = request.timestamp; + // let settlement_date = current_round.get_option_settlement_date(); + // if (timestamp < settlement_date - TIMESTAMP_TOLERANCE || timestamp > + // settlement_date) { + // // @dev Emit L1 request not fulfilled event + // self + // .emit( + // Event::L1RequestNotFulfilled( + // L1RequestNotFulfilled { id, caller, reason: 'REPLACE ME' } + // ) + // ); + // return false; + // } + // + // // @dev Pricing data can only be set if the correct script was ran + // (identifiers) and the // inputs (timestamp) & outputs (data) align with the + // supplied proof // @note Skipping for now until Fossil is further developed + // // verfifer_contract.prove_computation(program_hash: identifiers.at(...), + // inputs: + // // [timestamp], outputs: [twap, volatility, reserve_price]) + // + // // @dev Set l1 pricing data for upcoming round settlement + // self.l1_data.entry(current_round.get_round_id()).write(result.data); + // + // // @dev Emit L1 request fulfilled event + // self.emit(Event::L1RequestFulfilled(L1RequestFulfilled { id, caller })); + // + // true + // } + // + // fn fulfill_request_to_start_auction( + // ref self: ContractState, request: L1DataRequest, result: L1Result + // ) -> bool { + // // @dev Get the request's ID and caller fulfilling the request + // let id = generate_request_id(request); + // let caller = get_caller_address(); + // + // // @dev Pricing data can only be set if the current round is Open + // let current_round = self.get_round_dispatcher(self.current_round_id.read()); + // if current_round.get_state() != OptionRoundState::Open { + // return false; + // } + // + // // @dev A requst is valid to start an auction if it is the between the + // deployment and // auction start date + // let timestamp = request.timestamp; + // let deployment_date = current_round.get_deployment_date(); + // let auction_start_date = current_round.get_auction_start_date(); + // if (timestamp < deployment_date || timestamp > auction_start_date) { + // // @dev Emit L1 request not fulfilled event + // self + // .emit( + // Event::L1RequestNotFulfilled( + // L1RequestNotFulfilled { id, caller, reason: 'REPLACE ME' } + // ) + // ); + // return false; + // } + // + // // @dev Pricing data can only be set if the correct script was ran + // (identifiers) and the // inputs (timestamp) & outputs (data) align with the + // supplied proof // @note Skipping for now until Fossil is further developed + // // verfifer_contract.prove_computation(program_hash: identifiers.at(...), + // inputs: + // // [timestamp], outputs: [twap, volatility, reserve_price]) + // + // // @dev Set pricing data for the Open round + // current_round.set_pricing_data(self.l1_data_to_round_data(result.data)); + // + // // @dev Emit L1 request fulfilled event + // self.emit(Event::L1RequestFulfilled(L1RequestFulfilled { id, caller })); + // + // true + // } + fn start_auction(ref self: ContractState) -> u256 { // @dev Update all unlocked liquidity to locked let unlocked_liquidity_before_auction = self.vault_unlocked_balance.read(); @@ -524,32 +746,36 @@ mod Vault { } fn settle_round(ref self: ContractState) -> u256 { - // @dev Settle the current round + // @dev Get pricing data set for the current round's settlement let current_round_id = self.current_round_id.read(); + let l1_data = self.l1_data.entry(current_round_id).read(); + assert(l1_data != Default::default(), RoundErrors::PricingDataNotSet); + + // @dev Settle the current round and return the total payout let current_round = self.get_round_dispatcher(current_round_id); - // FOSSIL - let to = current_round.get_option_settlement_date(); - let from = to - TWAP_DURATION; - let settlement_price = self.fetch_TWAP_for_time_period(from, to); - let total_payout = current_round.settle_round(settlement_price); + let total_payout = current_round.settle_round(l1_data.twap); // @dev Calculate the remaining liquidity after the round settles let starting_liq = current_round.get_starting_liquidity(); let unsold_liq = current_round.get_unsold_liquidity(); let remaining_liq = starting_liq - unsold_liq - total_payout; - // @dev Calculate the amount of liquidity that was not stashed by liquidity providers, - // avoiding division by 0 + // @dev Calculate the amount of liquidity that was stashed/not stashed by liquidity + // providers, avoiding division by 0 let vault = get_contract_address(); - let starting_liq_queued = self.queued_liquidity.read((vault, current_round_id)); + let starting_liq_queued = self + .queued_liquidity + .entry(vault) + .entry(current_round_id) + .read(); let remaining_liq_stashed = match starting_liq.is_zero() { true => 0, false => (remaining_liq * starting_liq_queued) / starting_liq }; let remaining_liq_not_stashed = remaining_liq - remaining_liq_stashed; - // @dev All of the remaining liquidity becomes unlocked and any stashed liquidity is set - // aside and no longer participates in the protocol + // @dev All of the remaining liquidity becomes unlocked, any stashed liquidity is + // set aside and no longer participates in the protocol self.vault_locked_balance.write(0); self .vault_stashed_balance @@ -563,35 +789,18 @@ mod Vault { self.get_eth_dispatcher().transfer(current_round.contract_address, total_payout); } - // @dev Deploy next option round contract & update the current round id - self.deploy_next_round(settlement_price); + // @dev Deploy the next option round contract & update the current round id + self.deploy_next_round(l1_data); - // Return the total payout for the option round + // @dev Return the total payout of the settled round total_payout } - - // @note will probably remove this - fn update_round_params(ref self: ContractState) { - let current_round_id = self.current_round_id.read(); - let current_round = self.get_round_dispatcher(current_round_id); - - let cap_level = self.fetch_cap_level_for_round(current_round_id); - let reserve_price = self.fetch_reserve_price_for_round(current_round_id); - let twap_end = current_round.get_auction_start_date(); - let twap_start = twap_end - TWAP_DURATION; - let current_avg_basefee = self.fetch_TWAP_for_time_period(twap_start, twap_end); - let volatility = self.fetch_volatility_for_round(current_round_id); - let strike_price = calculate_strike_price( - self.vault_type.read(), current_avg_basefee, volatility - ); - - current_round.update_round_params(reserve_price, cap_level, strike_price); - } } // ************************************************************************* // INTERNAL FUNCTIONS // ************************************************************************* + #[generate_trait] impl InternalImpl of VaultInternalTrait { /// Get contract dispatchers @@ -600,10 +809,6 @@ mod Vault { ERC20ABIDispatcher { contract_address: self.eth_address.read() } } - fn get_market_aggregator_dispatcher(self: @ContractState) -> IMarketAggregatorDispatcher { - IMarketAggregatorDispatcher { contract_address: self.market_aggregator_address.read() } - } - fn get_round_dispatcher(self: @ContractState, round_id: u256) -> IOptionRoundDispatcher { IOptionRoundDispatcher { contract_address: self.round_addresses.read(round_id) } } @@ -651,62 +856,32 @@ mod Vault { /// Deploying rounds - fn calculate_dates(self: @ContractState) -> (u64, u64, u64) { - let now = starknet::get_block_timestamp(); - let auction_start_date = now + self.round_transition_period.read(); - let auction_end_date = auction_start_date + self.auction_run_time.read(); - let option_settlement_date = auction_end_date + self.option_run_time.read(); - - (auction_start_date, auction_end_date, option_settlement_date) - } - - fn deploy_first_round(ref self: ContractState) { - let now = starknet::get_block_timestamp(); - let TWAP_end_date = now; - let TWAP_start_date = now - TWAP_DURATION; - let current_avg_basefee = self - .fetch_TWAP_for_time_period(TWAP_start_date, TWAP_end_date); - - self.deploy_next_round(current_avg_basefee); - } + // @dev Deploy the next option round, if data is supplied, calculate the strike + // price and cap level and set the next round's data + fn deploy_next_round(ref self: ContractState, l1_data: L1Data) { + let vault_address: ContractAddress = get_contract_address(); + let round_id: u256 = self.current_round_id.read() + 1; - fn deploy_next_round(ref self: ContractState, current_avg_basefee: u256) { // @dev Create this round's constructor args let mut calldata: Array = array![]; - // @dev Get this vault's address - let vault_address: ContractAddress = get_contract_address(); - // @dev Cauclulate this round's id - let round_id: u256 = self.current_round_id.read() + 1; - // @dev Calcualte this round's dates - let (auction_start_date, auction_end_date, option_settlement_date) = self - .calculate_dates(); - // @dev Fetch this round's reserve price and cap level - let reserve_price = self.fetch_reserve_price_for_round(round_id); - let cap_level = self.fetch_cap_level_for_round(round_id); - // @dev Calculate this round's strike price - let volatility = self.fetch_volatility_for_round(round_id); - let strike_price = calculate_strike_price( - self.vault_type.read(), current_avg_basefee, volatility - ); + let pricing_data = self.l1_data_to_round_data(l1_data); + let constructor_args = OptionRoundConstructorArgs { + vault_address, round_id, pricing_data + }; - calldata.append_serde(vault_address); - calldata.append_serde(round_id); - calldata.append_serde(auction_start_date); - calldata.append_serde(auction_end_date); - calldata.append_serde(option_settlement_date); - calldata.append_serde(reserve_price); - calldata.append_serde(cap_level); - calldata.append_serde(strike_price); + calldata.append_serde(constructor_args); // @dev Deploy the round let (address, _) = deploy_syscall( self.option_round_class_hash.read(), 'some salt', calldata.span(), false ) .expect(Errors::OptionRoundDeploymentFailed); + let round = IOptionRoundDispatcher { contract_address: address }; // @dev Update the current round id self.current_round_id.write(round_id); - // @dev Set this round address + + // @dev Store this round address self.round_addresses.write(round_id, address); // @dev Emit option round deployed event @@ -716,12 +891,10 @@ mod Vault { OptionRoundDeployed { round_id, address, - reserve_price, - strike_price, - cap_level, - auction_start_date, - auction_end_date, - option_settlement_date + auction_start_date: round.get_auction_start_date(), + auction_end_date: round.get_auction_end_date(), + option_settlement_date: round.get_option_settlement_date(), + pricing_data } ) ); @@ -729,44 +902,16 @@ mod Vault { /// Fossil - fn fetch_reserve_price_for_round(self: @ContractState, round_id: u256) -> u256 { - let mk_agg = self.get_market_aggregator_dispatcher(); - let res = mk_agg.get_reserve_price_for_round(get_contract_address(), round_id); - match res { - Option::Some(reserve_price) => reserve_price, - //Option::None => panic!("No reserve price found") - Option::None => 0 - } - } + // @dev Converts L1 data from Fossil (or 3rd party) to pricing data for the round + fn l1_data_to_round_data(self: @ContractState, l1_data: L1Data) -> PricingData { + let L1Data { twap, volatility, reserve_price } = l1_data; + let alpha = self.alpha.read(); + let vault_type = self.vault_type.read(); - fn fetch_cap_level_for_round(self: @ContractState, round_id: u256) -> u128 { - let mk_agg = self.get_market_aggregator_dispatcher(); - let res = mk_agg.get_cap_level_for_round(get_contract_address(), round_id); - match res { - Option::Some(cap_level) => cap_level, - //Option::None => panic!("No cap level found") - Option::None => 0 - } - } + let cap_level = calculate_cap_level(alpha, volatility); + let strike_price = calculate_strike_price(vault_type, twap, volatility); - fn fetch_volatility_for_round(self: @ContractState, round_id: u256) -> u128 { - let mk_agg = self.get_market_aggregator_dispatcher(); - let res = mk_agg.get_volatility_for_round(get_contract_address(), round_id); - match res { - Option::Some(volatility) => volatility, - //Option::None => panic!("No volatility found") - Option::None => 0 - } - } - - fn fetch_TWAP_for_time_period(self: @ContractState, from: u64, to: u64) -> u256 { - let mk_agg = self.get_market_aggregator_dispatcher(); - let res = mk_agg.get_TWAP_for_time_period(from, to); - match res { - Option::Some(TWAP) => TWAP, - //Option::None => panic!("No TWAP found") - Option::None => 0 - } + PricingData { strike_price, cap_level, reserve_price } } /// Position management @@ -782,7 +927,7 @@ mod Vault { let mut realized_deposit = 0; while i < current_round_id { // @dev Increment the realized deposit by the account's deposit in this round - realized_deposit += self.positions.read((account, i)); + realized_deposit += self.positions.entry(account).entry(i).read(); // @dev Get the liquidity that became unlocked for the account in this round let account_unlocked_liq = self @@ -800,10 +945,9 @@ mod Vault { }; // @dev Add in the liquidity provider's current round deposit - realized_deposit + self.positions.read((account, current_round_id)) + realized_deposit + self.positions.entry(account).entry(current_round_id).read() } - // @dev Calculate the account's starting deposit for the current round and their deposit // for the upcoming round fn get_refreshed_position(self: @ContractState, account: ContractAddress) -> (u256, u256) { @@ -819,11 +963,13 @@ mod Vault { // @dev Get the account's upcoming round deposit let mut upcoming_round_deposit = self .positions - .read((account, current_round_id + 1)); + .entry(account) + .entry(current_round_id + 1) + .read(); // @dev There are only premium/unsold liquidity after the auction ends if state == OptionRoundState::Running { - // @dev Adds 0 if the premium/unsold liquidity was already moved as a deposit - // into the upcoming round + // @dev Adds 0 if the premium/unsold liquidity was already moved as a + // deposit into the upcoming round upcoming_round_deposit += self .get_liquidity_unlocked_for_account_in_round( account, current_round_deposit, current_round_id @@ -835,9 +981,9 @@ mod Vault { } } - // @dev Combine deposits from the last checkpoint into a single deposit for the current round, - // and if there are premiums/unsold liquidity collectable, add them as a deposit for the - // upcoming round + // @dev Combine deposits from the last checkpoint into a single deposit for the current + // round, and if there are premiums/unsold liquidity collectable, add them as a deposit for + // the upcoming round fn refresh_position(ref self: ContractState, account: ContractAddress) { // @dev Get the refreshed current and upcoming round deposits let current_round_id = self.current_round_id.read(); @@ -845,8 +991,12 @@ mod Vault { .get_refreshed_position(account); // @dev Update the account's current round deposit and checkpoint - if current_round_deposit != self.positions.read((account, current_round_id)) { - self.positions.write((account, current_round_id), current_round_deposit); + if current_round_deposit != self + .positions + .entry(account) + .entry(current_round_id) + .read() { + self.positions.entry(account).entry(current_round_id).write(current_round_deposit); } if current_round_id - 1 != self.position_checkpoints.read(account) { self.position_checkpoints.write(account, current_round_id - 1); @@ -857,14 +1007,21 @@ mod Vault { if self .get_round_dispatcher(current_round_id) .get_state() == OptionRoundState::Running { - if !self.is_premium_moved.read((account, current_round_id)) { - self.is_premium_moved.write((account, current_round_id), true); + // @dev If the premiums/unsold liquidity were not moved as a deposit into the + // next round, move them + if !self.is_premium_moved.entry(account).entry(current_round_id).read() { + self.is_premium_moved.entry(account).entry(current_round_id).write(true); + // @dev Update the account's upcoming round deposit if it has changed if upcoming_round_deposit != self .positions - .read((account, current_round_id + 1)) { + .entry(account) + .entry(current_round_id + 1) + .read() { self .positions - .write((account, current_round_id + 1), upcoming_round_deposit); + .entry(account) + .entry(current_round_id + 1) + .write(upcoming_round_deposit); } } } @@ -875,20 +1032,22 @@ mod Vault { // @param account_staring_liq: The liquidity the account locked at the start of the round // @param round_id: The round to lookup // @note Returns 0 if the round is Open | Running - // @note Returns 0 if the unlocked liq was moved as a deposit into the next round (refreshed) + // @note Returns 0 if the unlocked liq was moved as a deposit into the next round + // (refreshed) fn get_liquidity_unlocked_for_account_in_round( self: @ContractState, account: ContractAddress, account_starting_liq: u256, round_id: u256 ) -> u256 { - // @dev If the round is Open | Auctioning, there are no premiums/unsold liquidity yet, return 0 - // @dev If the unlocked liquidity was moved as a deposit into the next round, return 0 + // @dev If the round is Open | Auctioning, there are no premiums/unsold liquidity yet, + // return 0 @dev If the unlocked liquidity was moved as a deposit into the next round, + // return 0 let round = self.get_round_dispatcher(round_id); let state = round.get_state(); if state == OptionRoundState::Open || state == OptionRoundState::Auctioning - || self.is_premium_moved.read((account, round_id)) { + || self.is_premium_moved.entry(account).entry(round_id).read() { 0 } else { // @dev How much unlockable liquidity is there in the round @@ -903,8 +1062,8 @@ mod Vault { } } - // @dev Get the liquidity that remained for an account after a round settled that was not stashed - // @param account: The account in question + // @dev Get the liquidity that remained for an account after a round settled that was not + // stashed @param account: The account in question // @param account_staring_liq: The liquidity the account locked at the start of the round // @param round_id: The round to lookup // @note Returns 0 if the round is not Settled @@ -922,7 +1081,11 @@ mod Vault { let (round_starting_liq, round_remaining_liq, _) = self.get_round_outcome(round_id); // @dev Calculate the amount of liquidity the account stashed - let account_liq_queued = self.queued_liquidity.read((account, round_id)); + let account_liq_queued = self + .queued_liquidity + .entry(account) + .entry(round_id) + .read(); let account_remaining_liq_stashed = (round_remaining_liq * account_liq_queued) / round_starting_liq; diff --git a/src/vault/interface.cairo b/src/vault/interface.cairo index e3e04040..419bec94 100644 --- a/src/vault/interface.cairo +++ b/src/vault/interface.cairo @@ -1,9 +1,44 @@ -use starknet::{ContractAddress}; -use pitch_lake_starknet::{ - vault::{contract::Vault}, - market_aggregator::interface::{IMarketAggregator, IMarketAggregatorDispatcher}, - types::{VaultType, OptionRoundState} -}; +use starknet::{ContractAddress, ClassHash}; +use pitch_lake::option_round::interface::OptionRoundState; + +// @dev An enum for each type of Vault +#[derive(starknet::Store, Copy, Drop, Serde, PartialEq)] +enum VaultType { + InTheMoney, + AtTheMoney, + OutOfMoney, +} + +// @dev Request to settle/start a round +#[derive(Copy, Drop, Serde)] +struct L1DataRequest { + identifiers: Span, + timestamp: u64, +} + +// @dev Data returned from request +#[derive(Default, PartialEq, Copy, Drop, Serde, starknet::Store)] +struct L1Data { + twap: u256, + volatility: u128, + reserve_price: u256, +} + +// @dev Struct to send result and proving data to `fulfill_request()` +#[derive(Copy, Drop, Serde)] +struct L1Result { + data: L1Data, + proof: Span, +} + +// @dev Constructor arguments +#[derive(Drop, Serde)] +struct ConstructorArgs { + request_fulfiller: ContractAddress, + eth_address: ContractAddress, + option_round_class_hash: ClassHash, + vault_type: VaultType, // replace with strike level and alpha +} // The interface for the vault contract #[starknet::interface] @@ -13,21 +48,9 @@ trait IVault { // @dev Get the type of vault (ITM | ATM | OTM) fn get_vault_type(self: @TContractState) -> VaultType; - // @dev Get the market aggregator's address - fn get_market_aggregator_address(self: @TContractState) -> ContractAddress; - // @dev Get the ETH address fn get_eth_address(self: @TContractState) -> ContractAddress; - // @dev Get the amount of time an auction runs for - fn get_auction_run_time(self: @TContractState) -> u64; - - // @dev Get the amount of time an option round runs for - fn get_option_run_time(self: @TContractState) -> u64; - - // Get the amount of time till starting the next round's auction - fn get_round_transition_period(self: @TContractState) -> u64; - // @return the current option round id fn get_current_round_id(self: @TContractState) -> u256; @@ -66,6 +89,15 @@ trait IVault { // @dev The account's % (bps) queued for withdrawal once the current round settles fn get_account_queued_bps(self: @TContractState, account: ContractAddress) -> u16; + /// Fossil + + // @dev Get the earliest Fossil request required to settle the current round + fn get_request_to_settle_round(self: @TContractState) -> L1DataRequest; + + // @dev Get the earliest Fossil request required to start the current round's auction if not + // already set or refreshing the data + fn get_request_to_start_auction(self: @TContractState) -> L1DataRequest; + /// Writes /// /// Account functions @@ -80,8 +112,9 @@ trait IVault { // @return The caller's updated unlocked position fn withdraw(ref self: TContractState, amount: u256) -> u256; - // @dev The caller queues a % of their locked balance to be stashed once the current round settles - // @param bps: The percentage points <= 10,000 the account queues to stash when the round settles + // @dev The caller queues a % of their locked balance to be stashed once the current round + // settles @param bps: The percentage points <= 10,000 the account queues to stash when the + // round settles fn queue_withdrawal(ref self: TContractState, bps: u16); // @dev The caller withdraws all of an account's stashed liquidity for the account @@ -91,9 +124,8 @@ trait IVault { /// State transitions - // Update the params of the current round if there are newer data from Fossil - // @note Will probably remove this - fn update_round_params(ref self: TContractState); + // @dev Fulfill a pricing data request + fn fulfill_request(ref self: TContractState, request: L1DataRequest, result: L1Result); // @dev Start the current round's auction // @return The total options available in the auction