From 8d8439c0d2189eeb531475e7c99fd245597cc8c2 Mon Sep 17 00:00:00 2001 From: Jordy Romuald <87231934+JordyRo1@users.noreply.github.com> Date: Thu, 22 Aug 2024 15:04:50 +0200 Subject: [PATCH] Feat(contracts) fee mecanism (#4) * feat(contracts): fee mecanism init * feat(contracts): refactor * feat(address_whitelist): generalize address implementation * feat(oo): add whitelist user verification * fix(format): format code --- optimistic_oracle/Scarb.lock | 6 + optimistic_oracle/Scarb.toml | 3 +- .../contracts/common/address_whitelist.cairo | 110 ++++++--- .../contracts/data_verification/store.cairo | 8 +- .../base_escalation_manager.cairo | 4 +- .../src/contracts/interfaces.cairo | 17 +- .../src/contracts/mocks/mock_oracle.cairo | 209 ++++++++++++++++++ .../src/contracts/optimistic_oracle_v1.cairo | 60 +++-- .../src/examples/prediction_market.cairo | 8 +- optimistic_oracle/src/lib.cairo | 1 + optimistic_oracle/src/tests/setup.cairo | 13 +- .../src/tests/test_address_whitelist.cairo | 70 ++++-- .../src/tests/test_optimistic_oracle.cairo | 22 +- 13 files changed, 434 insertions(+), 97 deletions(-) create mode 100644 optimistic_oracle/src/contracts/mocks/mock_oracle.cairo diff --git a/optimistic_oracle/Scarb.lock b/optimistic_oracle/Scarb.lock index e810ea5..ccba4eb 100644 --- a/optimistic_oracle/Scarb.lock +++ b/optimistic_oracle/Scarb.lock @@ -64,9 +64,15 @@ version = "0.1.0" dependencies = [ "alexandria_bytes", "openzeppelin", + "pragma_lib", "snforge_std", ] +[[package]] +name = "pragma_lib" +version = "1.0.0" +source = "git+https://github.com/astraly-labs/pragma-lib#fe9a46743254182ac331da488ccfc05e0185cdd0" + [[package]] name = "snforge_std" version = "0.22.0" diff --git a/optimistic_oracle/Scarb.toml b/optimistic_oracle/Scarb.toml index 8433d04..09ecca3 100644 --- a/optimistic_oracle/Scarb.toml +++ b/optimistic_oracle/Scarb.toml @@ -9,6 +9,7 @@ edition = "2023_11" starknet = "2.6.3" openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git"} alexandria_bytes = { git = "https://github.com/keep-starknet-strange/alexandria.git" } +pragma_lib = { git = "https://github.com/astraly-labs/pragma-lib" } [dev-dependencies] @@ -21,4 +22,4 @@ sierra = true # Enable CASM codegen. casm = true # Emit Python-powered hints in order to run compiled CASM class with legacy Cairo VM. -casm-add-pythonic-hints = false \ No newline at end of file +casm-add-pythonic-hints = false diff --git a/optimistic_oracle/src/contracts/common/address_whitelist.cairo b/optimistic_oracle/src/contracts/common/address_whitelist.cairo index 81d302b..415667e 100644 --- a/optimistic_oracle/src/contracts/common/address_whitelist.cairo +++ b/optimistic_oracle/src/contracts/common/address_whitelist.cairo @@ -8,6 +8,7 @@ pub mod address_whitelist { }; use optimistic_oracle::contracts::interfaces::IAddressWhitelist; use openzeppelin::access::ownable::OwnableComponent; + use core::hash::{LegacyHash, HashStateTrait}; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); #[abi(embed_v0)] impl OwnableImpl = OwnableComponent::OwnableImpl; @@ -23,6 +24,25 @@ pub mod address_whitelist { In, } + #[derive(PartialEq, Drop, Serde, starknet::Store, Copy)] + pub enum WhitelistType { + Currency, + User, + } + + // Implement LegacyHash for (WhitelistType, ContractAddress) + impl WhitelistKeyHash of LegacyHash<(WhitelistType, ContractAddress)> { + fn hash(state: felt252, value: (WhitelistType, ContractAddress)) -> felt252 { + let (whitelist_type, address) = value; + let whitelist_type_felt252 = match whitelist_type { + WhitelistType::Currency => { 0 }, + WhitelistType::User => { 1 } + }; + let mut state = LegacyHash::::hash(state, whitelist_type_felt252); + LegacyHash::::hash(state, address) + } + } + // Store manual implementation (basic implementation panics if no default enum is defined for a given address, cf: https://github.com/starkware-libs/cairo/blob/b741c26c553fd9fa3246cee91fd5c637f225cdb9/crates/cairo-lang-starknet/src/plugin/derive/store.rs#L263) impl StatusStoreImpl of Store { @@ -86,16 +106,19 @@ pub mod address_whitelist { #[derive(starknet::Event, Drop)] pub struct AddedToWhitelist { pub added_address: ContractAddress, + pub whitelist_type: WhitelistType, } + #[derive(starknet::Event, Drop)] pub struct RemovedFromWhitelist { pub removed_address: ContractAddress, + pub whitelist_type: WhitelistType, } #[storage] struct Storage { - whitelist_indices: LegacyMap::, - whitelist: LegacyMap::, + whitelist_indices: LegacyMap::<(WhitelistType, ContractAddress), ContractAddress>, + whitelist: LegacyMap::<(WhitelistType, ContractAddress), Status>, #[substorage(v0)] ownable: OwnableComponent::Storage, #[substorage(v0)] @@ -110,55 +133,70 @@ pub mod address_whitelist { #[abi(embed_v0)] impl IAddressWhitelistImpl of IAddressWhitelist { - fn add_to_whitelist(ref self: ContractState, new_element: ContractAddress) { + fn add_to_whitelist( + ref self: ContractState, new_element: ContractAddress, whitelist_type: WhitelistType + ) { self.ownable.assert_only_owner(); self.reentrancy_guard.start(); - match self.get_status(new_element) { + match self.get_status(new_element, whitelist_type) { Option::Some(status) => { if (status == Status::In) { self.reentrancy_guard.end(); return; } else if (status == Status::Out) { - self.whitelist.write(new_element, Status::In); - self.emit(AddedToWhitelist { added_address: new_element }); + self.whitelist.write((whitelist_type, new_element), Status::In); + self.emit(AddedToWhitelist { added_address: new_element, whitelist_type }); } else { - self.insert_to_whitelist(new_element); - self.whitelist.write(new_element, Status::In); - self.emit(AddedToWhitelist { added_address: new_element }); + self.insert_to_whitelist(new_element, whitelist_type); + self.whitelist.write((whitelist_type, new_element), Status::In); + self.emit(AddedToWhitelist { added_address: new_element, whitelist_type }); } }, Option::None => { - self.insert_to_whitelist(new_element); - self.whitelist.write(new_element, Status::In); - self.emit(AddedToWhitelist { added_address: new_element }); + self.insert_to_whitelist(new_element, whitelist_type); + self.whitelist.write((whitelist_type, new_element), Status::In); + self.emit(AddedToWhitelist { added_address: new_element, whitelist_type }); } } self.reentrancy_guard.end(); } - fn remove_from_whitelist(ref self: ContractState, element_to_remove: ContractAddress) { + fn remove_from_whitelist( + ref self: ContractState, + element_to_remove: ContractAddress, + whitelist_type: WhitelistType + ) { self.ownable.assert_only_owner(); self.reentrancy_guard.start(); - if (self.whitelist.read(element_to_remove) != Status::Out) { - self.whitelist.write(element_to_remove, Status::Out); - self.emit(RemovedFromWhitelist { removed_address: element_to_remove }); + if (self.whitelist.read((whitelist_type, element_to_remove)) != Status::Out) { + self.whitelist.write((whitelist_type, element_to_remove), Status::Out); + self + .emit( + RemovedFromWhitelist { removed_address: element_to_remove, whitelist_type } + ); } self.reentrancy_guard.end(); } - fn is_on_whitelist(self: @ContractState, element_to_check: ContractAddress) -> bool { - self.whitelist.read(element_to_check) == Status::In + fn is_on_whitelist( + self: @ContractState, element_to_check: ContractAddress, whitelist_type: WhitelistType + ) -> bool { + self.whitelist.read((whitelist_type, element_to_check)) == Status::In } - fn get_whitelist(self: @ContractState) -> Span { - self.build_whitelist_indices_array() + fn get_whitelist( + self: @ContractState, whitelist_type: WhitelistType + ) -> Span { + self.build_whitelist_indices_array(whitelist_type) } } #[generate_trait] impl InternalTraitImpl of InternalTrait { - fn get_status(self: @ContractState, address: ContractAddress) -> Option { - match self.whitelist.read(address) { + fn get_status( + self: @ContractState, address: ContractAddress, whitelist_type: WhitelistType + ) -> Option { + match self.whitelist.read((whitelist_type, address)) { Status::None => Option::Some(Status::None), Status::In => Option::Some(Status::In), Status::Out => Option::Some(Status::Out), @@ -166,11 +204,14 @@ pub mod address_whitelist { } } - - fn find_last_whitelist_indice(self: @ContractState) -> ContractAddress { - let mut current_indice = self.whitelist_indices.read(0.try_into().unwrap()); + fn find_last_whitelist_indice( + self: @ContractState, whitelist_type: WhitelistType + ) -> ContractAddress { + let mut current_indice = self + .whitelist_indices + .read((whitelist_type, 0.try_into().unwrap())); loop { - let next_indice = self.whitelist_indices.read(current_indice); + let next_indice = self.whitelist_indices.read((whitelist_type, current_indice)); if next_indice == 0.try_into().unwrap() { break current_indice; } @@ -178,16 +219,17 @@ pub mod address_whitelist { } } - // Helper: builds a span of whitelist_indices from the storage map - fn build_whitelist_indices_array(self: @ContractState) -> Span { + fn build_whitelist_indices_array( + self: @ContractState, whitelist_type: WhitelistType + ) -> Span { let mut index = 0.try_into().unwrap(); let mut whitelist_indices = array![]; loop { - let indice = self.whitelist_indices.read(index); + let indice = self.whitelist_indices.read((whitelist_type, index)); if (indice == 0.try_into().unwrap()) { break (); } - if (self.whitelist.read(indice) == Status::In) { + if (self.whitelist.read((whitelist_type, indice)) == Status::In) { whitelist_indices.append(indice); } index = indice; @@ -196,9 +238,11 @@ pub mod address_whitelist { whitelist_indices.span() } - fn insert_to_whitelist(ref self: ContractState, new_element: ContractAddress) { - let last_index = self.find_last_whitelist_indice(); - self.whitelist_indices.write(last_index, new_element); + fn insert_to_whitelist( + ref self: ContractState, new_element: ContractAddress, whitelist_type: WhitelistType + ) { + let last_index = self.find_last_whitelist_indice(whitelist_type); + self.whitelist_indices.write((whitelist_type, last_index), new_element); } } } diff --git a/optimistic_oracle/src/contracts/data_verification/store.cairo b/optimistic_oracle/src/contracts/data_verification/store.cairo index a0bad34..6500124 100644 --- a/optimistic_oracle/src/contracts/data_verification/store.cairo +++ b/optimistic_oracle/src/contracts/data_verification/store.cairo @@ -4,7 +4,7 @@ pub mod store { use optimistic_oracle::contracts::interfaces::{IStoreDispatcher, IStore, IStoreDispatcherTrait}; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; - + use optimistic_oracle::contracts::optimistic_oracle_v1::optimistic_oracle_v1::ETH_ADDRESS; use openzeppelin::access::ownable::OwnableComponent; #[abi(embed_v0)] impl OwnableImpl = OwnableComponent::OwnableImpl; @@ -69,12 +69,12 @@ pub mod store { fn withdraw_funds(ref self: ContractState, receiver: ContractAddress) { self.ownable.assert_only_owner(); let eth_dispatcher = ERC20ABIDispatcher { - contract_address: 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 // ETH Contract Address - .try_into() - .unwrap() + contract_address: ETH_ADDRESS // ETH Contract Address + .try_into().unwrap() }; let balance = eth_dispatcher.balanceOf(starknet::get_contract_address()); eth_dispatcher.transfer(receiver, balance); } } } + diff --git a/optimistic_oracle/src/contracts/escalation_manager/base_escalation_manager.cairo b/optimistic_oracle/src/contracts/escalation_manager/base_escalation_manager.cairo index ce11fb1..a7e63e6 100644 --- a/optimistic_oracle/src/contracts/escalation_manager/base_escalation_manager.cairo +++ b/optimistic_oracle/src/contracts/escalation_manager/base_escalation_manager.cairo @@ -3,7 +3,7 @@ pub mod base_escalation_manager { use starknet::ContractAddress; use optimistic_oracle::contracts::interfaces::{ IOptimisticOracleDispatcher, IOptimisticOracleDispatcherTrait, AssertionPolicy, - IEscalationManager, IOptimisticOracleV3CallbackRecipient + IEscalationManager, IOptimisticOracleCallbackRecipient }; #[storage] @@ -63,7 +63,7 @@ pub mod base_escalation_manager { #[abi(embed_v0)] - impl IOptimisticOracleV3CallbackRecipientImpl of IOptimisticOracleV3CallbackRecipient< + impl IOptimisticOracleCallbackRecipientImpl of IOptimisticOracleCallbackRecipient< ContractState > { fn assertion_resolved_callback( diff --git a/optimistic_oracle/src/contracts/interfaces.cairo b/optimistic_oracle/src/contracts/interfaces.cairo index 87fdc37..c054c1c 100644 --- a/optimistic_oracle/src/contracts/interfaces.cairo +++ b/optimistic_oracle/src/contracts/interfaces.cairo @@ -5,6 +5,7 @@ use optimistic_oracle::contracts::mocks::oracle_ancillary::mock_oracle_ancillary QueryPoint, QueryIndex }; use optimistic_oracle::examples::prediction_market::prediction_market::Market; +use optimistic_oracle::contracts::common::address_whitelist::address_whitelist::WhitelistType; #[derive(starknet::Store, Drop, Serde, Copy)] pub struct EscalationManagerSettings { @@ -113,13 +114,19 @@ pub trait IIdentifierWhitelist { #[starknet::interface] pub trait IAddressWhitelist { - fn add_to_whitelist(ref self: TContractState, new_element: ContractAddress); + fn add_to_whitelist( + ref self: TContractState, new_element: ContractAddress, whitelist_type: WhitelistType + ); - fn remove_from_whitelist(ref self: TContractState, element_to_remove: ContractAddress); + fn remove_from_whitelist( + ref self: TContractState, element_to_remove: ContractAddress, whitelist_type: WhitelistType + ); - fn is_on_whitelist(self: @TContractState, element_to_check: ContractAddress) -> bool; + fn is_on_whitelist( + self: @TContractState, element_to_check: ContractAddress, whitelist_type: WhitelistType + ) -> bool; - fn get_whitelist(self: @TContractState) -> Span; + fn get_whitelist(self: @TContractState, whitelist_type: WhitelistType) -> Span; } #[starknet::interface] @@ -195,7 +202,7 @@ pub trait IMockOracleAncillaryConfiguration { #[starknet::interface] -pub trait IOptimisticOracleV3CallbackRecipient { +pub trait IOptimisticOracleCallbackRecipient { fn assertion_resolved_callback( ref self: TContractState, assertion_id: felt252, asserted_truthfully: bool ); diff --git a/optimistic_oracle/src/contracts/mocks/mock_oracle.cairo b/optimistic_oracle/src/contracts/mocks/mock_oracle.cairo new file mode 100644 index 0000000..8e59a08 --- /dev/null +++ b/optimistic_oracle/src/contracts/mocks/mock_oracle.cairo @@ -0,0 +1,209 @@ +#[starknet::contract] +pub mod mock_oracle { + use pragma_lib::abi::{IPragmaABIDispatcher, IPragmaABIDispatcherTrait, IPragmaABI}; + use pragma_lib::types::{ + DataType, PragmaPricesResponse, AggregationMode, Currency, Pair, PossibleEntries, + SimpleDataType, Checkpoint, SpotEntry, BaseEntry + }; + use starknet::{ClassHash, ContractAddress}; + pub const DEFAULT_PRICE: u128 = 250000000000; + #[storage] + struct Storage {} + #[abi(embed_v0)] + impl IPragmaABIImpl of IPragmaABI { + fn get_decimals(self: @ContractState, data_type: DataType) -> u32 { + 0 + } + + fn get_data_median(self: @ContractState, data_type: DataType) -> PragmaPricesResponse { + PragmaPricesResponse { + price: DEFAULT_PRICE, + decimals: 8, + last_updated_timestamp: starknet::get_block_timestamp(), + num_sources_aggregated: 1, + expiration_timestamp: Option::None + } + } + + fn get_data_median_for_sources( + self: @ContractState, data_type: DataType, sources: Span + ) -> PragmaPricesResponse { + PragmaPricesResponse { + price: 0, + decimals: 0, + last_updated_timestamp: 0, + num_sources_aggregated: 0, + expiration_timestamp: Option::None + } + } + + fn get_data( + self: @ContractState, data_type: DataType, aggregation_mode: AggregationMode + ) -> PragmaPricesResponse { + PragmaPricesResponse { + price: 0, + decimals: 0, + last_updated_timestamp: 0, + num_sources_aggregated: 0, + expiration_timestamp: Option::None + } + } + + fn get_data_median_multi( + self: @ContractState, data_types: Span, sources: Span + ) -> Span { + array![].span() + } + + fn get_data_entry( + self: @ContractState, data_type: DataType, source: felt252 + ) -> PossibleEntries { + PossibleEntries::Spot( + SpotEntry { + base: BaseEntry { timestamp: 0, source: source, publisher: 0 }, + pair_id: 0, + price: 0, + volume: 0 + } + ) + } + + + fn get_data_for_sources( + self: @ContractState, + data_type: DataType, + aggregation_mode: AggregationMode, + sources: Span + ) -> PragmaPricesResponse { + PragmaPricesResponse { + price: 0, + decimals: 0, + last_updated_timestamp: 0, + num_sources_aggregated: 0, + expiration_timestamp: Option::None + } + } + + fn get_data_entries(self: @ContractState, data_type: DataType) -> Span { + array![].span() + } + + fn get_data_entries_for_sources( + self: @ContractState, data_type: DataType, sources: Span + ) -> (Span, u64) { + (array![].span(), 0) + } + + fn get_last_checkpoint_before( + self: @ContractState, + data_type: DataType, + timestamp: u64, + aggregation_mode: AggregationMode, + ) -> (Checkpoint, u64) { + ( + Checkpoint { + timestamp: 0, + value: 0, + aggregation_mode: AggregationMode::Median, + num_sources_aggregated: 0 + }, + 0 + ) + } + + fn get_data_with_USD_hop( + self: @ContractState, + base_currency_id: felt252, + quote_currency_id: felt252, + aggregation_mode: AggregationMode, + typeof: SimpleDataType, + expiration_timestamp: Option:: + ) -> PragmaPricesResponse { + PragmaPricesResponse { + price: 0, + decimals: 0, + last_updated_timestamp: 0, + num_sources_aggregated: 0, + expiration_timestamp: Option::None + } + } + + fn get_publisher_registry_address(self: @ContractState) -> ContractAddress { + starknet::contract_address_const::<0>() + } + + fn get_latest_checkpoint_index( + self: @ContractState, data_type: DataType, aggregation_mode: AggregationMode + ) -> (u64, bool) { + (0, false) + } + + fn get_latest_checkpoint( + self: @ContractState, data_type: DataType, aggregation_mode: AggregationMode + ) -> Checkpoint { + Checkpoint { + timestamp: 0, + value: 0, + aggregation_mode: AggregationMode::Median, + num_sources_aggregated: 0 + } + } + + fn get_checkpoint( + self: @ContractState, + data_type: DataType, + checkpoint_index: u64, + aggregation_mode: AggregationMode + ) -> Checkpoint { + Checkpoint { + timestamp: 0, + value: 0, + aggregation_mode: AggregationMode::Median, + num_sources_aggregated: 0 + } + } + + fn get_sources_threshold(self: @ContractState,) -> u32 { + 0 + } + + fn get_admin_address(self: @ContractState,) -> ContractAddress { + starknet::contract_address_const::<0>() + } + + + fn publish_data(ref self: ContractState, new_entry: PossibleEntries) {} + + fn publish_data_entries(ref self: ContractState, new_entries: Span) {} + + fn set_admin_address(ref self: ContractState, new_admin_address: ContractAddress) {} + + fn update_publisher_registry_address( + ref self: ContractState, new_publisher_registry_address: ContractAddress + ) {} + + fn add_currency(ref self: ContractState, new_currency: Currency) {} + + fn update_currency(ref self: ContractState, currency_id: felt252, currency: Currency) {} + + + fn add_pair(ref self: ContractState, new_pair: Pair) {} + + fn set_checkpoint( + ref self: ContractState, data_type: DataType, aggregation_mode: AggregationMode + ) {} + + fn set_checkpoints( + ref self: ContractState, data_types: Span, aggregation_mode: AggregationMode + ) {} + + + fn set_sources_threshold(ref self: ContractState, threshold: u32) {} + + fn upgrade(ref self: ContractState, impl_hash: ClassHash) {} + + fn get_implementation_hash(self: @ContractState) -> ClassHash { + 0.try_into().unwrap() + } + } +} diff --git a/optimistic_oracle/src/contracts/optimistic_oracle_v1.cairo b/optimistic_oracle/src/contracts/optimistic_oracle_v1.cairo index 34e646f..b4ce1e5 100644 --- a/optimistic_oracle/src/contracts/optimistic_oracle_v1.cairo +++ b/optimistic_oracle/src/contracts/optimistic_oracle_v1.cairo @@ -7,9 +7,12 @@ pub mod optimistic_oracle_v1 { IAddressWhitelistDispatcher, IAddressWhitelistDispatcherTrait, IStoreDispatcher, IStoreDispatcherTrait, Assertion, EscalationManagerSettings, AssertionPolicy, IEscalationManagerDispatcher, IEscalationManagerDispatcherTrait, IOracleAncillaryDispatcher, - IOracleAncillaryDispatcherTrait, IOptimisticOracleV3CallbackRecipientDispatcher, - IOptimisticOracleV3CallbackRecipientDispatcherTrait, IOptimisticOracleV3CallbackRecipient + IOracleAncillaryDispatcherTrait, IOptimisticOracleCallbackRecipientDispatcher, + IOptimisticOracleCallbackRecipientDispatcherTrait, IOptimisticOracleCallbackRecipient }; + use optimistic_oracle::contracts::common::address_whitelist::address_whitelist::WhitelistType; + use pragma_lib::abi::{IPragmaABIDispatcher, IPragmaABIDispatcherTrait}; + use pragma_lib::types::DataType; use openzeppelin::access::ownable::OwnableComponent; use alexandria_data_structures::array_ext::ArrayTraitExt; use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; @@ -28,21 +31,23 @@ pub mod optimistic_oracle_v1 { use starknet::{ContractAddress, ClassHash, contract_address_const}; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); - + component!( + path: ReentrancyGuardComponent, storage: reentrancy_guard, event: ReentrancyGuardEvent + ); #[abi(embed_v0)] impl OwnableImpl = OwnableComponent::OwnableImpl; impl OwnableInternalImpl = OwnableComponent::InternalImpl; impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; - component!( - path: ReentrancyGuardComponent, storage: reentrancy_guard, event: ReentrancyGuardEvent - ); - // CONSTANTS DEFINITION pub const DEFAULT_IDENTIFIER: felt252 = 'ASSERT_TRUTH'; pub const NUMERICAL_TRUE: u256 = 1000000000000000000; pub const BURNED_BOND_PERCENTAGE: u256 = 500000000000000000; - + pub const ASSERTION_FEE: u128 = 100000000; + pub const ORACLE_ADDRESS: felt252 = + 0x36031daa264c24520b11d93af622c848b2499b66b41d611bac95e13cfca131a; //TODO: WHEN REDEPLOYING CHANGE TO MAINNET ADDRESS + pub const ETH_ADDRESS: felt252 = + 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7; #[storage] struct Storage { finder: IFinderDispatcher, @@ -83,6 +88,7 @@ pub mod optimistic_oracle_v1 { pub const ASSERTION_NOT_SETTLED: felt252 = 'Assertion not settled'; pub const CURRENCY_NOT_DEFINED: felt252 = 'Currency not defined'; pub const INSUFFICIENT_ALLOWANCE: felt252 = 'Insufficient allowance'; + pub const FETCHING_PRICE_ERROR: felt252 = 'Error fetching price'; } #[event] @@ -225,6 +231,18 @@ pub mod optimistic_oracle_v1 { escalation_manager, identifier ); + // Retreive the 1 dollar fee + + let mut eth_assertion_fee = 0; + let caller = starknet::get_caller_address(); + if (!self.get_collateral_whitelist().is_on_whitelist(caller, WhitelistType::User)) { + let oracle_dispatcher = IPragmaABIDispatcher { + contract_address: ORACLE_ADDRESS.try_into().unwrap() + }; + let response = oracle_dispatcher.get_data_median(DataType::SpotEntry('ETH/USD')); + assert(response.price > 0, Errors::FETCHING_PRICE_ERROR); + eth_assertion_fee = dollar_to_wei(ASSERTION_FEE, response.price, response.decimals); + } assert(asserter != contract_address_const::<0>(), Errors::ASSERTER_CANNOT_BE_ZERO); let assertion = self.assertions.read(assertion_id); assert( @@ -244,7 +262,8 @@ pub mod optimistic_oracle_v1 { let contract_address = starknet::get_contract_address(); assert( - currency.allowance(caller_address, contract_address) >= bond, + currency.allowance(caller_address, contract_address) >= bond + + eth_assertion_fee.into(), Errors::INSUFFICIENT_ALLOWANCE ); let assertion_policy = self.get_assertion_policy(assertion_id); @@ -275,7 +294,8 @@ pub mod optimistic_oracle_v1 { expiration_time: time + liveness } ); - currency.transfer_from(caller_address, contract_address, bond); + currency + .transfer_from(caller_address, contract_address, bond + eth_assertion_fee.into()); self .emit( AssertionMade { @@ -457,7 +477,9 @@ pub mod optimistic_oracle_v1 { identifier, self.get_identifier_whitelist().is_identifier_supported(identifier) ); let whitelisted_currency = WhitelistedCurrency { - is_whitelisted: self.get_collateral_whitelist().is_on_whitelist(currency), + is_whitelisted: self + .get_collateral_whitelist() + .is_on_whitelist(currency, WhitelistType::Currency), final_fee: self.get_store().compute_final_fee(currency) }; self.cached_currencies.write(currency, whitelisted_currency); @@ -554,7 +576,9 @@ pub mod optimistic_oracle_v1 { if (self.cached_currencies.read(currency).is_whitelisted) { return true; } - let is_whitelisted = self.get_collateral_whitelist().is_on_whitelist(currency); + let is_whitelisted = self + .get_collateral_whitelist() + .is_on_whitelist(currency, WhitelistType::Currency); let final_fee = self.get_store().compute_final_fee(currency); let cached_currency = WhitelistedCurrency { is_whitelisted, final_fee: final_fee }; self.cached_currencies.write(currency, cached_currency); @@ -648,11 +672,11 @@ pub mod optimistic_oracle_v1 { let cr = self.assertions.read(assertion_id).callback_recipient; let em = self.get_escalation_manager(assertion_id); if (cr != contract_address_const::<0>()) { - IOptimisticOracleV3CallbackRecipientDispatcher { contract_address: cr } + IOptimisticOracleCallbackRecipientDispatcher { contract_address: cr } .assertion_resolved_callback(assertion_id, asserted_truthfully); } if (em != contract_address_const::<0>()) { - IOptimisticOracleV3CallbackRecipientDispatcher { contract_address: em } + IOptimisticOracleCallbackRecipientDispatcher { contract_address: em } .assertion_resolved_callback(assertion_id, asserted_truthfully); } } @@ -661,16 +685,20 @@ pub mod optimistic_oracle_v1 { let cr = self.assertions.read(assertion_id).callback_recipient; let em = self.get_escalation_manager(assertion_id); if (cr != contract_address_const::<0>()) { - IOptimisticOracleV3CallbackRecipientDispatcher { contract_address: cr } + IOptimisticOracleCallbackRecipientDispatcher { contract_address: cr } .assertion_disputed_callback(assertion_id); } if (em != contract_address_const::<0>()) { - IOptimisticOracleV3CallbackRecipientDispatcher { contract_address: em } + IOptimisticOracleCallbackRecipientDispatcher { contract_address: em } .assertion_disputed_callback(assertion_id); } } } + fn dollar_to_wei(usd: u128, price: u128, decimals: u32) -> u128 { + (usd * pow(10, decimals.into()) * 1000000000000000000) / (price * 100000000) + } + fn get_id( claim: @ByteArray, diff --git a/optimistic_oracle/src/examples/prediction_market.cairo b/optimistic_oracle/src/examples/prediction_market.cairo index b7b44f4..c2e5709 100644 --- a/optimistic_oracle/src/examples/prediction_market.cairo +++ b/optimistic_oracle/src/examples/prediction_market.cairo @@ -4,10 +4,11 @@ pub mod prediction_market { use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; use optimistic_oracle::contracts::interfaces::{ IFinderDispatcher, IFinderDispatcherTrait, IOptimisticOracleDispatcherTrait, - IOptimisticOracleDispatcher, IOptimisticOracleV3CallbackRecipient, IPredictionMarket, + IOptimisticOracleDispatcher, IOptimisticOracleCallbackRecipient, IPredictionMarket, IExtendedERC20Dispatcher, IExtendedERC20DispatcherTrait, IAddressWhitelistDispatcher, IAddressWhitelistDispatcherTrait }; + use optimistic_oracle::contracts::common::address_whitelist::address_whitelist::WhitelistType; use optimistic_oracle::contracts::utils::keccak::compute_keccak_byte_array; use optimistic_oracle::contracts::mocks::full_erc20::full_erc20::{MINTER_ROLE, BURNER_ROLE}; use optimistic_oracle::contracts::optimistic_oracle_v1::optimistic_oracle_v1::DEFAULT_IDENTIFIER; @@ -137,7 +138,8 @@ pub mod prediction_market { ) { self.finder.write(IFinderDispatcher { contract_address: finder }); assert( - self.get_collateral_whitelist().is_on_whitelist(currency), Errors::UNSUPPORTED_CURRENCY + self.get_collateral_whitelist().is_on_whitelist(currency, WhitelistType::Currency), + Errors::UNSUPPORTED_CURRENCY ); self.currency.write(ERC20ABIDispatcher { contract_address: currency }); self.oo.write(IOptimisticOracleDispatcher { contract_address: optimistic_oracle }); @@ -146,7 +148,7 @@ pub mod prediction_market { } #[abi(embed_v0)] - impl IOptimisticOracleV3CallbackRecipientImpl of IOptimisticOracleV3CallbackRecipient< + impl IOptimisticOracleCallbackRecipientImpl of IOptimisticOracleCallbackRecipient< ContractState > { fn assertion_resolved_callback( diff --git a/optimistic_oracle/src/lib.cairo b/optimistic_oracle/src/lib.cairo index c38fecb..3b79911 100644 --- a/optimistic_oracle/src/lib.cairo +++ b/optimistic_oracle/src/lib.cairo @@ -23,6 +23,7 @@ pub mod contracts { pub mod oracle_ancillary; pub mod full_erc20; pub mod mock_erc20; + pub mod mock_oracle; } } pub mod examples { diff --git a/optimistic_oracle/src/tests/setup.cairo b/optimistic_oracle/src/tests/setup.cairo index 0824568..3700eb1 100644 --- a/optimistic_oracle/src/tests/setup.cairo +++ b/optimistic_oracle/src/tests/setup.cairo @@ -2,7 +2,7 @@ use snforge_std::{ declare, ContractClassTrait, start_prank, stop_prank, CheatTarget, EventSpy, EventAssertions, spy_events, SpyOn }; - +use optimistic_oracle::contracts::common::address_whitelist::address_whitelist::WhitelistType; use starknet::{ContractAddress, contract_address_const, EthAddress}; use optimistic_oracle::contracts::interfaces::{ IFinderDispatcher, IFinderDispatcherTrait, IOptimisticOracleDispatcher, @@ -11,12 +11,13 @@ use optimistic_oracle::contracts::interfaces::{ IMockOracleAncillaryConfigurationDispatcher, IStoreDispatcher, IStoreDispatcherTrait, }; use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher}; +use optimistic_oracle::contracts::optimistic_oracle_v1::optimistic_oracle_v1::ORACLE_ADDRESS; use openzeppelin::utils::serde::SerializedAppend; use openzeppelin::access::ownable::interface::{IOwnableDispatcher, IOwnableDispatcherTrait}; use optimistic_oracle::contracts::utils::constants::OracleInterfaces; -pub const INITIAL_SUPPLY: u256 = 10000000000; +pub const INITIAL_SUPPLY: u256 = 1000000000000000000000000000; pub const DEFAULT_LIVENESS: u64 = 1000; pub const FINAL_FEE: u256 = 10000; @@ -79,6 +80,7 @@ pub fn setup_optimistic_oracle( erc20: ERC20ABIDispatcher, finder: IFinderDispatcher, default_liveness: u64 ) -> (IOptimisticOracleDispatcher, EventSpy) { let optimistic_oracle_class = declare("optimistic_oracle_v1").unwrap(); + setup_mock_oracle(); let res = optimistic_oracle_class .deploy( @array![ @@ -96,6 +98,11 @@ pub fn setup_optimistic_oracle( (IOptimisticOracleDispatcher { contract_address: optimistic_oracle_addr }, spy) } +fn setup_mock_oracle() { + let mock_oracle_class = declare("mock_oracle").unwrap(); + mock_oracle_class.deploy_at(@array![], ORACLE_ADDRESS.try_into().unwrap()).unwrap(); +} + pub fn setup_mock_oracle_ancillary( finder: IFinderDispatcher @@ -129,7 +136,7 @@ pub fn oo_full_config() -> IOptimisticOracleDispatcher { identifier_whitelist.add_supported_identifier(OracleInterfaces::IDENTIFIER_WHITELIST); let ownable = IOwnableDispatcher { contract_address: address_whitelist.contract_address }; start_prank(CheatTarget::One(ownable.contract_address), OWNER()); - address_whitelist.add_to_whitelist(erc20.contract_address); + address_whitelist.add_to_whitelist(erc20.contract_address, WhitelistType::Currency); let ownable = IOwnableDispatcher { contract_address: finder.contract_address }; start_prank(CheatTarget::One(ownable.contract_address), OWNER()); finder diff --git a/optimistic_oracle/src/tests/test_address_whitelist.cairo b/optimistic_oracle/src/tests/test_address_whitelist.cairo index 437704f..03f5112 100644 --- a/optimistic_oracle/src/tests/test_address_whitelist.cairo +++ b/optimistic_oracle/src/tests/test_address_whitelist.cairo @@ -7,6 +7,7 @@ use optimistic_oracle::contracts::{ interfaces::{IAddressWhitelistDispatcher, IAddressWhitelistDispatcherTrait} }; use snforge_std::cheatcodes::events::EventAssertions; +use optimistic_oracle::contracts::common::address_whitelist::address_whitelist::WhitelistType; #[test] @@ -33,32 +34,44 @@ fn test_address_whitelist_add_to_whitelist() { let (address_whitelist, mut spy) = setup_address_whitelist(); let ownable = IOwnableDispatcher { contract_address: address_whitelist.contract_address }; start_prank(CheatTarget::One(ownable.contract_address), OWNER()); - address_whitelist.add_to_whitelist(address_to_add); + address_whitelist.add_to_whitelist(address_to_add, WhitelistType::Currency); let expected_event = address_whitelist::Event::AddedToWhitelist( - address_whitelist::AddedToWhitelist { added_address: address_to_add } + address_whitelist::AddedToWhitelist { + added_address: address_to_add, whitelist_type: WhitelistType::Currency + } ); spy.assert_emitted(@array![(address_whitelist.contract_address, expected_event)]); let mut expected_result = array![address_to_add]; - assert(address_whitelist.get_whitelist() == expected_result.span(), 'AW: Insertion failed'); - assert_eq!(address_whitelist.is_on_whitelist(address_to_add), true); + assert( + address_whitelist.get_whitelist(WhitelistType::Currency) == expected_result.span(), + 'AW: Insertion failed' + ); + assert_eq!(address_whitelist.is_on_whitelist(address_to_add, WhitelistType::Currency), true); // 2nd insertion let new_address_to_add = contract_address_const::<0x1231413>(); - address_whitelist.add_to_whitelist(new_address_to_add); + address_whitelist.add_to_whitelist(new_address_to_add, WhitelistType::Currency); expected_result.append(new_address_to_add); let expected_event = address_whitelist::Event::AddedToWhitelist( - address_whitelist::AddedToWhitelist { added_address: new_address_to_add } + address_whitelist::AddedToWhitelist { + added_address: new_address_to_add, whitelist_type: WhitelistType::Currency + } ); spy.assert_emitted(@array![(address_whitelist.contract_address, expected_event)]); - assert(address_whitelist.get_whitelist() == expected_result.span(), 'AW: 2 Insertion failed'); - assert_eq!(address_whitelist.is_on_whitelist(new_address_to_add), true); + assert( + address_whitelist.get_whitelist(WhitelistType::Currency) == expected_result.span(), + 'AW: 2 Insertion failed' + ); + assert_eq!( + address_whitelist.is_on_whitelist(new_address_to_add, WhitelistType::Currency), true + ); // Duplicate insertion - address_whitelist.add_to_whitelist(address_to_add); + address_whitelist.add_to_whitelist(address_to_add, WhitelistType::Currency); assert( - address_whitelist.get_whitelist() == expected_result.span(), + address_whitelist.get_whitelist(WhitelistType::Currency) == expected_result.span(), 'AW: Duplicate insertion failed' ); - assert_eq!(address_whitelist.is_on_whitelist(address_to_add), true); + assert_eq!(address_whitelist.is_on_whitelist(address_to_add, WhitelistType::Currency), true); } @@ -69,27 +82,40 @@ fn test_address_whitelist_remove_from_whitelist() { let (address_whitelist, mut spy) = setup_address_whitelist(); let ownable = IOwnableDispatcher { contract_address: address_whitelist.contract_address }; start_prank(CheatTarget::One(ownable.contract_address), OWNER()); - address_whitelist.add_to_whitelist(address_to_add); + address_whitelist.add_to_whitelist(address_to_add, WhitelistType::Currency); let expected_event = address_whitelist::Event::AddedToWhitelist( - address_whitelist::AddedToWhitelist { added_address: address_to_add } + address_whitelist::AddedToWhitelist { + added_address: address_to_add, whitelist_type: WhitelistType::Currency + } ); spy.assert_emitted(@array![(address_whitelist.contract_address, expected_event)]); let mut expected_result = array![address_to_add]; - assert(address_whitelist.get_whitelist() == expected_result.span(), 'AW: Insertion failed'); + assert( + address_whitelist.get_whitelist(WhitelistType::Currency) == expected_result.span(), + 'AW: Insertion failed' + ); // Removal - address_whitelist.remove_from_whitelist(address_to_add); - assert(address_whitelist.get_whitelist() == array![].span(), 'AW: Removal failed'); - assert_eq!(address_whitelist.is_on_whitelist(address_to_add), false); + address_whitelist.remove_from_whitelist(address_to_add, WhitelistType::Currency); + assert( + address_whitelist.get_whitelist(WhitelistType::Currency) == array![].span(), + 'AW: Removal failed' + ); + assert_eq!(address_whitelist.is_on_whitelist(address_to_add, WhitelistType::Currency), false); let expected_event = address_whitelist::Event::RemovedFromWhitelist( - address_whitelist::RemovedFromWhitelist { removed_address: address_to_add } + address_whitelist::RemovedFromWhitelist { + removed_address: address_to_add, whitelist_type: WhitelistType::Currency + } ); spy.assert_emitted(@array![(address_whitelist.contract_address, expected_event)]); // Reintroduction - address_whitelist.add_to_whitelist(address_to_add); - assert(address_whitelist.get_whitelist() == expected_result.span(), 'AW: Reinsertion failed'); - assert_eq!(address_whitelist.is_on_whitelist(address_to_add), true); + address_whitelist.add_to_whitelist(address_to_add, WhitelistType::Currency); + assert( + address_whitelist.get_whitelist(WhitelistType::Currency) == expected_result.span(), + 'AW: Reinsertion failed' + ); + assert_eq!(address_whitelist.is_on_whitelist(address_to_add, WhitelistType::Currency), true); } #[test] @@ -97,6 +123,6 @@ fn test_address_whitelist_remove_from_whitelist() { fn test_address_whitelist_add_to_whitelist_fails_if_caller_not_owner() { let address_to_add = contract_address_const::<0x234e2>(); let (address_whitelist, _) = setup_address_whitelist(); - address_whitelist.add_to_whitelist(address_to_add); + address_whitelist.add_to_whitelist(address_to_add, WhitelistType::Currency); } diff --git a/optimistic_oracle/src/tests/test_optimistic_oracle.cairo b/optimistic_oracle/src/tests/test_optimistic_oracle.cairo index 21f1d4c..9fe86fa 100644 --- a/optimistic_oracle/src/tests/test_optimistic_oracle.cairo +++ b/optimistic_oracle/src/tests/test_optimistic_oracle.cairo @@ -11,13 +11,15 @@ use optimistic_oracle::contracts::{ IOptimisticOracleDispatcher, IOptimisticOracleDispatcherTrait, IStoreDispatcherTrait, IFinderDispatcherTrait, IIdentifierWhitelistDispatcherTrait, IAddressWhitelistDispatcherTrait, IMockOracleAncillaryConfigurationDispatcherTrait - } + }, + optimistic_oracle_v1::optimistic_oracle_v1, }; -use optimistic_oracle::contracts::optimistic_oracle_v1::optimistic_oracle_v1; +use optimistic_oracle::contracts::common::address_whitelist::address_whitelist::WhitelistType; use snforge_std::cheatcodes::events::EventAssertions; use optimistic_oracle::contracts::utils::constants::OracleInterfaces; use openzeppelin::token::erc20::interface::{ERC20ABI, ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +const DEFAULT_PRICE: u256 = 400000000000000; #[test] fn test_oo_owner_verification() { let oo = oo_full_config(); @@ -59,7 +61,7 @@ fn test_oo_assert_truth_with_default() { identifier_whitelist.add_supported_identifier(optimistic_oracle_v1::DEFAULT_IDENTIFIER); let ownable = IOwnableDispatcher { contract_address: address_whitelist.contract_address }; start_prank(CheatTarget::One(ownable.contract_address), OWNER()); - address_whitelist.add_to_whitelist(erc20.contract_address); + address_whitelist.add_to_whitelist(erc20.contract_address, WhitelistType::Currency); let ownable = IOwnableDispatcher { contract_address: finder.contract_address }; start_prank(CheatTarget::One(ownable.contract_address), OWNER()); finder @@ -83,7 +85,7 @@ fn test_oo_assert_truth_with_default() { let minimum_bond = oo.get_minimum_bond(erc20.contract_address); let ownable_erc20 = IOwnableDispatcher { contract_address: erc20.contract_address }; start_prank(CheatTarget::One(ownable_erc20.contract_address), OWNER()); - erc20.approve(oo.contract_address, minimum_bond); + erc20.approve(oo.contract_address, minimum_bond + DEFAULT_PRICE.into()); stop_prank(CheatTarget::One(ownable_erc20.contract_address)); let ownable = IOwnableDispatcher { contract_address: oo.contract_address }; start_prank(CheatTarget::One(ownable.contract_address), OWNER()); @@ -101,7 +103,7 @@ fn test_oo_assert_truth_with_default() { assert_eq!(assertion.settlement_resolution, false); assert_eq!(assertion.assertion_time, time); assert_eq!(assertion.expiration_time, time + liveness); - assert_eq!(erc20.balanceOf(OWNER()), INITIAL_SUPPLY - minimum_bond); + assert_eq!(erc20.balanceOf(OWNER()), INITIAL_SUPPLY - minimum_bond - DEFAULT_PRICE.into()); // now we can settle the assertion, without dispute start_warp(CheatTarget::One(oo.contract_address), starknet::get_block_timestamp() + liveness); @@ -118,12 +120,12 @@ fn test_oo_assert_truth_with_default() { assert_eq!(assertion.settlement_resolution, true); assert_eq!(assertion.assertion_time, time); assert_eq!(assertion.expiration_time, time + liveness); - assert_eq!(erc20.balanceOf(OWNER()), INITIAL_SUPPLY); + assert_eq!(erc20.balanceOf(OWNER()), INITIAL_SUPPLY - DEFAULT_PRICE.into()); stop_warp(CheatTarget::One(oo.contract_address)); // now we initiate a new assertion with a dispute process, but first, we need to provide the disputer with the necessary funds start_prank(CheatTarget::One(ownable_erc20.contract_address), OWNER()); - erc20.approve(oo.contract_address, minimum_bond); + erc20.approve(oo.contract_address, minimum_bond + DEFAULT_PRICE.into()); stop_prank(CheatTarget::One(ownable_erc20.contract_address)); claim.append_word(0x1234, 2); let assertion_id = oo.assert_truth_with_defaults(claim, OWNER()); @@ -139,6 +141,7 @@ fn test_oo_assert_truth_with_default() { ); oo.dispute_assertion(assertion_id, DISPUTER()); + let assertion = oo.get_assertion(assertion_id); assert_eq!(assertion.asserter, OWNER()); assert_eq!(assertion.disputer, DISPUTER()); @@ -163,6 +166,7 @@ fn test_oo_assert_truth_with_default() { // once dispute is done, we can settle and conclude the process oo.settle_assertion(assertion_id); + let assertion = oo.get_assertion(assertion_id); assert_eq!(assertion.asserter, OWNER()); assert_eq!(assertion.disputer, DISPUTER()); @@ -177,7 +181,9 @@ fn test_oo_assert_truth_with_default() { assert_eq!(assertion.expiration_time, time + liveness); assert_eq!(erc20.balanceOf(DISPUTER()), 0); // So the owner balance should be the initial amount - the amount sent to the oracle (because the owner sent minimum_bond to the disputer before the dispute process) - assert_eq!(erc20.balanceOf(OWNER()), INITIAL_SUPPLY - minimum_bond / 2); + assert_eq!( + erc20.balanceOf(OWNER()), INITIAL_SUPPLY - minimum_bond / 2 - 2 * DEFAULT_PRICE.into() + ); assert_eq!(erc20.balanceOf(store.contract_address), minimum_bond / 2); stop_warp(CheatTarget::One(oo.contract_address)); }