diff --git a/book/src/smart-contracts-architecture/utils-module.md b/book/src/smart-contracts-architecture/utils-module.md index d497077a..98b2881f 100644 --- a/book/src/smart-contracts-architecture/utils-module.md +++ b/book/src/smart-contracts-architecture/utils-module.md @@ -32,6 +32,8 @@ It contains the following files: - [precision.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/precision.cairo): This offers utility functions for detailed math and changing units, helping with accurate calculations and conversions between different measures, like from float to wei, applying factors, and managing rounding in the Satoru Starknet smart contract environment. +- [serializable_dict.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/serializable_dict.cairo): This file defines the SerializableFelt252Dict structure that allows us to use a Felt252Dict and serialize/deserialize it. + - [span32.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/span32.cairo): Provides utility functions for managing and manipulating fixed-size arrays (span32). A wrapper around Span type with a maximum size of 32. Used to prevent size overflow when storing Span. - [starknet_utils.cairo](https://github.com/keep-starknet-strange/satoru/blob/main/src/utils/starknet_utils.cairo): This puts in place fake utilities to mimic Starknet environment features, like `gasleft` and `tx.gasprice`, in the Satoru Starknet smart contract environment. These functions give back set values based on the given parameters, allowing a way to mimic Starknet gas actions during testing and development. diff --git a/src/callback/callback_utils.cairo b/src/callback/callback_utils.cairo index 21f46091..6d74cafe 100644 --- a/src/callback/callback_utils.cairo +++ b/src/callback/callback_utils.cairo @@ -23,7 +23,7 @@ use starknet::ContractAddress; // Local imports. use satoru::data::data_store::{IDataStoreDispatcher, IDataStoreDispatcherTrait}; use satoru::data::keys; -use satoru::event::event_utils::LogData; +use satoru::event::event_utils::{LogData, LogDataTrait}; use satoru::order::order::Order; use satoru::deposit::deposit::Deposit; use satoru::withdrawal::withdrawal::Withdrawal; @@ -92,14 +92,14 @@ fn get_saved_callback_contract( /// * `key` - They key of the deposit. /// * `deposit` - The deposit that was executed. /// * `log_data` - The log data. -fn after_deposit_execution(key: felt252, deposit: Deposit, log_data: LogData) { +fn after_deposit_execution(key: felt252, deposit: Deposit, mut log_data: LogData) { if !is_valid_callback_contract(deposit.callback_contract) { return; } let dispatcher = IDepositCallbackReceiverDispatcher { contract_address: deposit.callback_contract }; - dispatcher.after_deposit_execution(key, deposit, log_data) + dispatcher.after_deposit_execution(key, deposit, log_data.serialize_into()) } /// Called after a deposit cancellation. @@ -107,14 +107,14 @@ fn after_deposit_execution(key: felt252, deposit: Deposit, log_data: LogData) { /// * `key` - They key of the deposit. /// * `deposit` - The deposit that was cancelled. /// * `log_data` - The log data. -fn after_deposit_cancellation(key: felt252, deposit: Deposit, log_data: LogData) { +fn after_deposit_cancellation(key: felt252, deposit: Deposit, mut log_data: LogData) { if !is_valid_callback_contract(deposit.callback_contract) { return; } let dispatcher = IDepositCallbackReceiverDispatcher { contract_address: deposit.callback_contract }; - dispatcher.after_deposit_cancellation(key, deposit, log_data) + dispatcher.after_deposit_cancellation(key, deposit, log_data.serialize_into()) } /// Called after a withdrawal execution. @@ -122,14 +122,14 @@ fn after_deposit_cancellation(key: felt252, deposit: Deposit, log_data: LogData) /// * `key` - They key of the withdrawal. /// * `withdrawal` - The withdrawal that was executed. /// * `log_data` - The log data. -fn after_withdrawal_execution(key: felt252, withdrawal: Withdrawal, log_data: LogData) { +fn after_withdrawal_execution(key: felt252, withdrawal: Withdrawal, mut log_data: LogData) { if !is_valid_callback_contract(withdrawal.callback_contract) { return; } let dispatcher = IWithdrawalCallbackReceiverDispatcher { contract_address: withdrawal.callback_contract }; - dispatcher.after_withdrawal_execution(key, withdrawal, log_data) + dispatcher.after_withdrawal_execution(key, withdrawal, log_data.serialize_into()) } /// Called after an withdrawal cancellation. @@ -137,14 +137,14 @@ fn after_withdrawal_execution(key: felt252, withdrawal: Withdrawal, log_data: Lo /// * `key` - They key of the withdrawal. /// * `withdrawal` - The withdrawal that was cancelled. /// * `log_data` - The log data. -fn after_withdrawal_cancellation(key: felt252, withdrawal: Withdrawal, log_data: LogData) { +fn after_withdrawal_cancellation(key: felt252, withdrawal: Withdrawal, mut log_data: LogData) { if !is_valid_callback_contract(withdrawal.callback_contract) { return; } let dispatcher = IWithdrawalCallbackReceiverDispatcher { contract_address: withdrawal.callback_contract }; - dispatcher.after_withdrawal_cancellation(key, withdrawal, log_data) + dispatcher.after_withdrawal_cancellation(key, withdrawal, log_data.serialize_into()) } /// Called after an order execution. @@ -152,12 +152,12 @@ fn after_withdrawal_cancellation(key: felt252, withdrawal: Withdrawal, log_data: /// * `key` - They key of the order. /// * `order` - The order that was executed. /// * `log_data` - The log data. -fn after_order_execution(key: felt252, order: Order, log_data: LogData) { +fn after_order_execution(key: felt252, order: Order, mut log_data: LogData) { if !is_valid_callback_contract(order.callback_contract) { return; } let dispatcher = IOrderCallbackReceiverDispatcher { contract_address: order.callback_contract }; - dispatcher.after_order_execution(key, order, log_data) + dispatcher.after_order_execution(key, order, log_data.serialize_into()) } /// Called after an order cancellation. @@ -165,12 +165,12 @@ fn after_order_execution(key: felt252, order: Order, log_data: LogData) { /// * `key` - They key of the order. /// * `order` - The order that was cancelled. /// * `log_data` - The log data. -fn after_order_cancellation(key: felt252, order: Order, log_data: LogData) { +fn after_order_cancellation(key: felt252, order: Order, mut log_data: LogData) { if !is_valid_callback_contract(order.callback_contract) { return; } let dispatcher = IOrderCallbackReceiverDispatcher { contract_address: order.callback_contract }; - dispatcher.after_order_cancellation(key, order, log_data) + dispatcher.after_order_cancellation(key, order, log_data.serialize_into()) } /// Called after an order cancellation. @@ -178,12 +178,12 @@ fn after_order_cancellation(key: felt252, order: Order, log_data: LogData) { /// * `key` - They key of the order. /// * `order` - The order that was frozen. /// * `log_data` - The log data. -fn after_order_frozen(key: felt252, order: Order, log_data: LogData) { +fn after_order_frozen(key: felt252, order: Order, mut log_data: LogData) { if !is_valid_callback_contract(order.callback_contract) { return; } let dispatcher = IOrderCallbackReceiverDispatcher { contract_address: order.callback_contract }; - dispatcher.after_order_frozen(key, order, log_data) + dispatcher.after_order_frozen(key, order, log_data.serialize_into()) } /// Validates that the given address is a contract. diff --git a/src/callback/deposit_callback_receiver/interface.cairo b/src/callback/deposit_callback_receiver/interface.cairo index de3d21f6..e45b7b33 100644 --- a/src/callback/deposit_callback_receiver/interface.cairo +++ b/src/callback/deposit_callback_receiver/interface.cairo @@ -13,7 +13,7 @@ trait IDepositCallbackReceiver { /// * `event_data` - The event log data. /// * `deposit` - The deposit that was executed. fn after_deposit_execution( - ref self: TContractState, key: felt252, deposit: Deposit, log_data: LogData, + ref self: TContractState, key: felt252, deposit: Deposit, log_data: Array, ); /// Called after a deposit cancellation. @@ -22,6 +22,6 @@ trait IDepositCallbackReceiver { /// * `event_data` - The event log data. /// * `deposit` - The deposit that was cancelled. fn after_deposit_cancellation( - ref self: TContractState, key: felt252, deposit: Deposit, log_data: LogData, + ref self: TContractState, key: felt252, deposit: Deposit, log_data: Array, ); } diff --git a/src/callback/mocks.cairo b/src/callback/mocks.cairo index e34d0438..f4f66d4b 100644 --- a/src/callback/mocks.cairo +++ b/src/callback/mocks.cairo @@ -35,13 +35,13 @@ mod CallbackMock { #[external(v0)] impl IDepositCallbackReceiverImpl of IDepositCallbackReceiver { fn after_deposit_execution( - ref self: ContractState, key: felt252, deposit: Deposit, log_data: LogData, + ref self: ContractState, key: felt252, deposit: Deposit, log_data: Array, ) { self.counter.write(self.get_counter() + 1); } fn after_deposit_cancellation( - ref self: ContractState, key: felt252, deposit: Deposit, log_data: LogData, + ref self: ContractState, key: felt252, deposit: Deposit, log_data: Array, ) { self.counter.write(self.get_counter() + 1); } diff --git a/src/callback/order_callback_receiver/interface.cairo b/src/callback/order_callback_receiver/interface.cairo index f1cc1944..8d1c8912 100644 --- a/src/callback/order_callback_receiver/interface.cairo +++ b/src/callback/order_callback_receiver/interface.cairo @@ -13,7 +13,7 @@ trait IOrderCallbackReceiver { /// * `order` - The order that was executed. /// * `log_data` - The log data. fn after_order_execution( - ref self: TContractState, key: felt252, order: Order, log_data: LogData + ref self: TContractState, key: felt252, order: Order, log_data: Array ); /// Called after an order cancellation. @@ -22,7 +22,7 @@ trait IOrderCallbackReceiver { /// * `order` - The order that was cancelled. /// * `log_data` - The log data. fn after_order_cancellation( - ref self: TContractState, key: felt252, order: Order, log_data: LogData + ref self: TContractState, key: felt252, order: Order, log_data: Array ); /// Called after an order cancellation. @@ -30,5 +30,7 @@ trait IOrderCallbackReceiver { /// * `key` - They key of the order. /// * `order` - The order that was frozen. /// * `log_data` - The log data. - fn after_order_frozen(ref self: TContractState, key: felt252, order: Order, log_data: LogData); + fn after_order_frozen( + ref self: TContractState, key: felt252, order: Order, log_data: Array + ); } diff --git a/src/callback/withdrawal_callback_receiver/interface.cairo b/src/callback/withdrawal_callback_receiver/interface.cairo index 9f73f468..1c43a31f 100644 --- a/src/callback/withdrawal_callback_receiver/interface.cairo +++ b/src/callback/withdrawal_callback_receiver/interface.cairo @@ -14,7 +14,7 @@ trait IWithdrawalCallbackReceiver { /// * `log_data` - The log data. // TODO uncomment withdrawal when available fn after_withdrawal_execution( - ref self: TContractState, key: felt252, withdrawal: Withdrawal, log_data: LogData, + ref self: TContractState, key: felt252, withdrawal: Withdrawal, log_data: Array, ); /// Called after an withdrawal cancellation. @@ -23,6 +23,6 @@ trait IWithdrawalCallbackReceiver { /// * `withdrawal` - The withdrawal that was cancelled. /// * `log_data` - The log data. fn after_withdrawal_cancellation( - ref self: TContractState, key: felt252, withdrawal: Withdrawal, log_data: LogData, + ref self: TContractState, key: felt252, withdrawal: Withdrawal, log_data: Array, ); } diff --git a/src/deposit/deposit_utils.cairo b/src/deposit/deposit_utils.cairo index e5d3719a..d884a367 100644 --- a/src/deposit/deposit_utils.cairo +++ b/src/deposit/deposit_utils.cairo @@ -184,7 +184,7 @@ fn cancel_deposit( event_emitter.emit_deposit_cancelled(key, reason, reason_bytes.span()); - let log_data: LogData = Default::default(); + let mut log_data: LogData = Default::default(); after_deposit_cancellation(key, deposit, log_data); gas_utils::pay_execution_fee_deposit( diff --git a/src/deposit/execute_deposit_utils.cairo b/src/deposit/execute_deposit_utils.cairo index a44d0112..4d426ad5 100644 --- a/src/deposit/execute_deposit_utils.cairo +++ b/src/deposit/execute_deposit_utils.cairo @@ -20,7 +20,10 @@ use satoru::deposit::{ deposit_vault::{IDepositVaultDispatcher, IDepositVaultDispatcherTrait}, error::DepositError }; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; -use satoru::event::event_utils::{LogData, set_item_uint_items, UintItems}; +use satoru::event::event_utils::{ + LogData, LogDataTrait, Felt252IntoU128, ContractAddressDictValue, I128252DictValue +}; +use satoru::utils::serializable_dict::{SerializableFelt252Dict, SerializableFelt252DictTrait}; use satoru::fee::fee_utils; use satoru::gas::gas_utils::pay_execution_fee_deposit; use satoru::market::{ @@ -237,9 +240,8 @@ fn execute_deposit(params: ExecuteDepositParams) { cache.received_market_tokens, ); - let event_data: LogData = Default::default(); - let mut uint_items: UintItems = Default::default(); - set_item_uint_items(uint_items, 0, 'received_market_tokens', cache.received_market_tokens); + let mut event_data: LogData = Default::default(); + event_data.uint_dict.insert_single('received_market_tokens', cache.received_market_tokens); after_deposit_execution(params.key, deposit, event_data); pay_execution_fee_deposit( diff --git a/src/event/event_utils.cairo b/src/event/event_utils.cairo index a7ec710d..f2df87d3 100644 --- a/src/event/event_utils.cairo +++ b/src/event/event_utils.cairo @@ -1,294 +1,200 @@ -use starknet::{get_caller_address, ContractAddress, contract_address_const}; +use starknet::{ + get_caller_address, ContractAddress, Felt252TryIntoContractAddress, ContractAddressIntoFelt252, + contract_address_const +}; use array::ArrayTrait; use satoru::utils::i128::i128; use traits::Default; use satoru::utils::traits::ContractAddressDefault; -//TODO Switch the append with a set in the functions when its available -#[derive(Drop, Serde)] -struct EventLogData { - cant_be_empty: u128, // remove -// TODO -} - -#[derive(Default, Serde, Drop)] -struct LogData { - address_items: AddressItems, - uint_items: UintItems, - int_items: IntItems, - bool_items: BoolItems, - felt252_items: Felt252Items, - array_of_felt_items: ArrayOfFeltItems, - string_items: StringItems, -} - -//ContractAddress -#[derive(Default, Serde, Drop)] -struct AddressItems { - items: Array, - array_items: Array, -} - -#[derive(Default, Serde, Drop)] -struct AddressKeyValue { - key: felt252, - value: ContractAddress, -} - -#[derive(Default, Serde, Drop)] -struct AddressArrayKeyValue { - key: felt252, - value: Array, -} - -//u128 - -#[derive(Default, Serde, Drop)] -struct UintItems { - items: Array, - array_items: Array, -} - -#[derive(Default, Serde, Drop)] -struct UintKeyValue { - key: felt252, - value: u128, -} - -#[derive(Default, Serde, Drop)] -struct UintArrayKeyValue { - key: felt252, - value: Array, -} - -//i128 -#[derive(Default, Serde, Drop)] -struct IntItems { - items: Array, - array_items: Array, -} - -#[derive(Default, Serde, Drop)] -struct IntKeyValue { - key: felt252, - value: i128, -} - -#[derive(Default, Serde, Drop)] -struct IntArrayKeyValue { - key: felt252, - value: Array, -} +use satoru::utils::serializable_dict::{SerializableFelt252Dict, SerializableFelt252DictTrait}; -//bool -#[derive(Default, Serde, Drop)] -struct BoolItems { - items: Array, - array_items: Array, -} +use alexandria_data_structures::array_ext::SpanTraitExt; -#[derive(Default, Serde, Drop)] -struct BoolKeyValue { - key: felt252, - value: bool, -} -#[derive(Default, Serde, Drop)] -struct BoolArrayKeyValue { - key: felt252, - value: Array, -} +// +// NEEDED IMPLEMENTATIONS FOR LOGDATA TYPES +// -//Felt252 -#[derive(Default, Serde, Drop)] -struct Felt252Items { - items: Array, - array_items: Array, +impl Felt252IntoBool of Into { + #[inline(always)] + fn into(self: felt252) -> bool { + let as_u128: u128 = self.try_into().expect('u128 Overflow'); + as_u128 > 0 + } } -#[derive(Default, Serde, Drop)] -struct Felt252KeyValue { - key: felt252, - value: felt252, +impl Felt252IntoU128 of Into { + #[inline(always)] + fn into(self: felt252) -> u128 { + self.try_into().expect('u128 Overflow') + } } -#[derive(Default, Serde, Drop)] -struct Felt252ArrayKeyValue { - key: felt252, - value: Array, +impl Felt252IntoI128 of Into { + #[inline(always)] + fn into(self: felt252) -> i128 { + self.try_into().expect('i128 Overflow') + } } -//Array of Felt -#[derive(Default, Serde, Drop)] -struct ArrayOfFeltItems { - items: Array, - array_items: Array, +impl Felt252IntoContractAddress of Into { + #[inline(always)] + fn into(self: felt252) -> ContractAddress { + Felt252TryIntoContractAddress::try_into(self).expect('contractaddress overflow') + } } -#[derive(Default, Serde, Drop)] -struct ArrayOfFeltKeyValue { - key: felt252, - value: Array, -} -#[derive(Default, Serde, Drop)] -struct ArrayOfFeltArrayKeyValue { - key: felt252, - value: Array>, +impl I128252DictValue of Felt252DictValue { + #[inline(always)] + fn zero_default() -> i128 nopanic { + i128 { mag: 0, sign: false } + } } -//String switch later -#[derive(Default, Serde, Drop)] -struct StringItems { - items: Array, - array_items: Array, +impl ContractAddressDictValue of Felt252DictValue { + #[inline(always)] + fn zero_default() -> ContractAddress nopanic { + contract_address_const::<0>() + } } -#[derive(Default, Serde, Drop)] -struct StringKeyValue { - key: felt252, - value: felt252, -} - -#[derive(Default, Serde, Drop)] -struct StringArrayKeyValue { - key: felt252, - value: Array, -} - -//TODO for the functions we need to implement the set instead of append and use the set with index. - -//AddressItems +// +// LOG DATA IMPLEMENTATION +// -fn set_item_address_items( - mut items: AddressItems, index: u32, key: felt252, value: ContractAddress -) -> AddressItems { - let address_key_value: AddressKeyValue = AddressKeyValue { key, value }; - let mut address: AddressItems = items; - address.items.append(address_key_value); - return address; -} - -fn set_item_array_address_items( - mut items: AddressItems, index: u32, key: felt252, value: Array -) -> AddressItems { - let address_array_key_value: AddressArrayKeyValue = AddressArrayKeyValue { key, value }; - let mut array_address: AddressItems = items; - array_address.array_items.append(address_array_key_value); - return array_address; -} - -// Uint - -fn set_item_uint_items(mut items: UintItems, index: u32, key: felt252, value: u128) -> UintItems { - let uint_key_value: UintKeyValue = UintKeyValue { key, value }; - let mut address: UintItems = items; - address.items.append(uint_key_value); - return address; -} - -fn set_item_array_uint_items( - mut items: UintItems, index: u32, key: felt252, value: Array -) -> UintItems { - let uint_array_key_value: UintArrayKeyValue = UintArrayKeyValue { key, value }; - let mut array_address: UintItems = items; - array_address.array_items.append(uint_array_key_value); - return array_address; -} - -// in128 - -fn set_item_int_items(mut items: IntItems, index: u32, key: felt252, value: i128) -> IntItems { - let int_key_value: IntKeyValue = IntKeyValue { key, value }; - let mut address: IntItems = items; - address.items.append(int_key_value); - return address; -} - -fn set_item_array_int_items( - mut items: IntItems, index: u32, key: felt252, value: Array -) -> IntItems { - let int_array_key_value: IntArrayKeyValue = IntArrayKeyValue { key, value }; - let mut array_address: IntItems = items; - array_address.array_items.append(int_array_key_value); - return array_address; -} - -// bool - -fn set_item_bool_items(mut items: BoolItems, index: u32, key: felt252, value: bool) -> BoolItems { - let bool_key_value: BoolKeyValue = BoolKeyValue { key, value }; - let mut address: BoolItems = items; - address.items.append(bool_key_value); - return address; -} - -fn set_item_array_bool_items( - mut items: BoolItems, index: u32, key: felt252, value: Array -) -> BoolItems { - let bool_array_key_value: BoolArrayKeyValue = BoolArrayKeyValue { key, value }; - let mut array_address: BoolItems = items; - array_address.array_items.append(bool_array_key_value); - return array_address; -} - -// felt252 - -fn set_item_Felt252_items( - mut items: Felt252Items, index: u32, key: felt252, value: felt252 -) -> Felt252Items { - let felt252_key_value: Felt252KeyValue = Felt252KeyValue { key, value }; - let mut address: Felt252Items = items; - address.items.append(felt252_key_value); - return address; -} - -fn set_item_array_Felt252_items( - mut items: Felt252Items, index: u32, key: felt252, value: Array -) -> Felt252Items { - let felt252_array_key_value: Felt252ArrayKeyValue = Felt252ArrayKeyValue { key, value }; - let mut array_address: Felt252Items = items; - array_address.array_items.append(felt252_array_key_value); - return array_address; -} - -// array of felt - -fn set_item_array_of_felt_items_items( - mut items: ArrayOfFeltItems, index: u32, key: felt252, value: Array -) -> ArrayOfFeltItems { - let array_of_felt_items_key_value: ArrayOfFeltKeyValue = ArrayOfFeltKeyValue { key, value }; - let mut address: ArrayOfFeltItems = items; - address.items.append(array_of_felt_items_key_value); - return address; +//TODO Switch the append with a set in the functions when its available +#[derive(Default, Serde, Destruct)] +struct EventLogData { + cant_be_empty: u128, // remove +// TODO } -fn set_item_array_array_of_felt_items( - mut items: ArrayOfFeltItems, index: u32, key: felt252, value: Array> -) -> ArrayOfFeltItems { - let array_of_felt_array_key_value: ArrayOfFeltArrayKeyValue = ArrayOfFeltArrayKeyValue { - key, value +#[derive(Default, Destruct)] +struct LogData { + address_dict: SerializableFelt252Dict, + uint_dict: SerializableFelt252Dict, + int_dict: SerializableFelt252Dict, + bool_dict: SerializableFelt252Dict, + felt252_dict: SerializableFelt252Dict, + string_dict: SerializableFelt252Dict +} + +/// Number of dicts presents in LogData +const DICTS_IN_LOGDATA: usize = 6; + +/// When serializing dicts into a unique Array, this is the value that will +/// be used to recognized a separation between two dicts. +const END_OF_DICT: felt252 = '______'; + +#[generate_trait] +impl LogDataImpl of LogDataTrait { + /// Serializes all the sub-dicts of LogData & append all of them into a new felt252 array + fn serialize(ref self: LogData, ref output: Array) { + let mut serialized_dicts: Array> = array![ + self.address_dict.serialize_into(), + self.uint_dict.serialize_into(), + self.int_dict.serialize_into(), + self.bool_dict.serialize_into(), + self.felt252_dict.serialize_into(), + self.string_dict.serialize_into() + ]; + let mut span_arrays = serialized_dicts.span(); + loop { + match span_arrays.pop_front() { + Option::Some(arr) => { + let mut sub_array_span = arr.span(); + loop { + match sub_array_span.pop_front() { + Option::Some(v) => { + output.append(*v); + }, + Option::None => { + break; + } + }; + }; + output.append(END_OF_DICT); + }, + Option::None => { + break; + } + }; + }; + } + + /// Serializes all the sub-dicts of LogData & return the serialized data + fn serialize_into(ref self: LogData) -> Array { + let mut serialized_data: Array = array![]; + self.serialize(ref serialized_data); + serialized_data + } + + /// Deserialize all the sub-dicts serialized into a LogData + fn deserialize(ref serialized: Span) -> Option { + // There should be the right amount of dictionnaries serialized + if serialized.occurrences_of(END_OF_DICT) != DICTS_IN_LOGDATA { + panic_with_felt252('serialized format error'); + } + + // Deserialize all dicts one by one + let mut serialized_dict = get_next_dict_serialized(ref serialized); + let address_dict = SerializableFelt252DictTrait::::deserialize( + ref serialized_dict + ) + .expect('deserialize err address'); + + let mut serialized_dict = get_next_dict_serialized(ref serialized); + let uint_dict = SerializableFelt252DictTrait::::deserialize(ref serialized_dict) + .expect('deserialize err uint'); + + let mut serialized_dict = get_next_dict_serialized(ref serialized); + let int_dict = SerializableFelt252DictTrait::::deserialize(ref serialized_dict) + .expect('deserialize err int'); + + let mut serialized_dict = get_next_dict_serialized(ref serialized); + let bool_dict = SerializableFelt252DictTrait::::deserialize(ref serialized_dict) + .expect('deserialize err bool'); + + let mut serialized_dict = get_next_dict_serialized(ref serialized); + let felt252_dict = SerializableFelt252DictTrait::::deserialize(ref serialized_dict) + .expect('deserialize err felt252'); + + let mut serialized_dict = get_next_dict_serialized(ref serialized); + let string_dict = SerializableFelt252DictTrait::::deserialize(ref serialized_dict) + .expect('deserialize err string'); + + // Create the LogData struct with every dicts + let log_data: LogData = LogData { + address_dict, uint_dict, int_dict, bool_dict, felt252_dict, string_dict + }; + + Option::Some(log_data) + } +} + + +// +// UTILITY FUNCTION +// + +/// Pop every elements from the span until the next occurences of END_OF_DICT or +/// the end of the Span and return those values in a Span. +fn get_next_dict_serialized(ref serialized: Span) -> Span { + let mut dict_data: Array = array![]; + loop { + match serialized.pop_front() { + Option::Some(v) => { + if *v == END_OF_DICT { + break; + } else { + dict_data.append(*v); + } + }, + Option::None => { + break; + } + }; }; - let mut array_address: ArrayOfFeltItems = items; - array_address.array_items.append(array_of_felt_array_key_value); - return array_address; -} - -// string - -fn set_item_string_items( - mut items: StringItems, index: u32, key: felt252, value: felt252 -) -> StringItems { - let string_key_value: StringKeyValue = StringKeyValue { key, value }; - let mut address: StringItems = items; - address.items.append(string_key_value); - return address; -} - -fn set_item_array_string_items( - mut items: StringItems, index: u32, key: felt252, value: Array -) -> StringItems { - let string_array_key_value: StringArrayKeyValue = StringArrayKeyValue { key, value }; - let mut array_address: StringItems = items; - array_address.array_items.append(string_array_key_value); - return array_address; + dict_data.span() } diff --git a/src/lib.cairo b/src/lib.cairo index 2e3cdd5d..1252bed5 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -153,6 +153,7 @@ mod utils { mod starknet_utils; mod traits; mod default; + mod serializable_dict; } // `liquidation` function to help with liquidations. diff --git a/src/order/decrease_order_utils.cairo b/src/order/decrease_order_utils.cairo index 22c2e777..ec734611 100644 --- a/src/order/decrease_order_utils.cairo +++ b/src/order/decrease_order_utils.cairo @@ -19,8 +19,11 @@ use satoru::position::position_utils; use satoru::position::position::Position; use satoru::swap::swap_utils::{SwapParams}; use satoru::position::position_utils::UpdatePositionParams; -use satoru::event::event_utils::LogData; -use satoru::event::event_utils; +use satoru::event::event_utils::{ + LogData, LogDataTrait, Felt252IntoU128, Felt252IntoContractAddress, ContractAddressDictValue, + I128252DictValue +}; +use satoru::utils::serializable_dict::{SerializableFelt252Dict, SerializableFelt252DictTrait}; use satoru::market::market_token::{IMarketTokenDispatcher, IMarketTokenDispatcherTrait}; use satoru::utils::span32::{Span32, Array32Trait}; use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; @@ -31,7 +34,7 @@ use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherT #[inline(always)] fn process_order( params: ExecuteOrderParams -) -> event_utils::LogData { //TODO check with refactor with callback_utils +) -> LogData { //TODO check with refactor with callback_utils let order: Order = params.order; market_utils::validate_position_market_check(params.contracts.data_store, params.market); @@ -239,30 +242,14 @@ fn get_output_event_data( output_amount: u128, secondary_output_token: ContractAddress, secondary_output_amount: u128 -) -> event_utils::LogData { - let mut address_items: event_utils::AddressItems = Default::default(); - let mut uint_items: event_utils::UintItems = Default::default(); - - address_items = - event_utils::set_item_address_items(address_items, 0, 'output_token', output_token); - address_items = - event_utils::set_item_address_items( - address_items, 1, 'secondary_output_token', secondary_output_token - ); +) -> LogData { + let mut log_data: LogData = Default::default(); - uint_items = event_utils::set_item_uint_items(uint_items, 0, 'output_amount', output_amount); - uint_items = - event_utils::set_item_uint_items( - uint_items, 1, 'secondary_output_amount', secondary_output_amount - ); + log_data.address_dict.insert_single('output_token', output_token); + log_data.address_dict.insert_single('secondary_output_token', secondary_output_token); - event_utils::LogData { - address_items, - uint_items, - int_items: Default::default(), - bool_items: Default::default(), - felt252_items: Default::default(), - array_of_felt_items: Default::default(), - string_items: Default::default(), - } + log_data.uint_dict.insert_single('output_amount', output_amount); + log_data.uint_dict.insert_single('secondary_output_amount', secondary_output_amount); + + log_data } diff --git a/src/order/order_utils.cairo b/src/order/order_utils.cairo index 00c896a9..e2668772 100644 --- a/src/order/order_utils.cairo +++ b/src/order/order_utils.cairo @@ -21,7 +21,11 @@ use satoru::token::token_utils; use satoru::callback::callback_utils; use satoru::gas::gas_utils; use satoru::order::order::{Order, OrderType, OrderTrait}; -use satoru::event::event_utils::LogData; +use satoru::event::event_utils::{ + LogData, LogDataTrait, Felt252IntoU128, Felt252IntoContractAddress, ContractAddressDictValue, + I128252DictValue +}; +use satoru::utils::serializable_dict::{SerializableFelt252Dict, SerializableFelt252DictTrait}; use satoru::order::error::OrderError; use satoru::order::{increase_order_utils, decrease_order_utils, swap_order_utils}; @@ -171,7 +175,7 @@ fn execute_order(params: ExecuteOrderParams) { secondary_order_type: params.secondary_order_type }; - let event_data: LogData = process_order(params_process); + let mut event_data: LogData = process_order(params_process); // validate that internal state changes are correct before calling // external callbacks @@ -266,7 +270,7 @@ fn cancel_order( event_emitter.emit_order_cancelled(key, reason, reason_bytes.span()); - let event_data = Default::default(); + let mut event_data: LogData = Default::default(); callback_utils::after_order_cancellation(key, order, event_data); gas_utils::pay_execution_fee_order( @@ -319,7 +323,7 @@ fn freeze_order( event_emitter.emit_order_frozen(key, reason, reason_bytes.span()); - let event_data = Default::default(); + let mut event_data: LogData = Default::default(); callback_utils::after_order_frozen(key, order, event_data); gas_utils::pay_execution_fee_order( diff --git a/src/order/swap_order_utils.cairo b/src/order/swap_order_utils.cairo index d9741cb5..b79e146c 100644 --- a/src/order/swap_order_utils.cairo +++ b/src/order/swap_order_utils.cairo @@ -7,14 +7,18 @@ use satoru::order::order::OrderType; use satoru::oracle::oracle_utils; use satoru::utils::arrays::u64_are_gte; use satoru::swap::swap_utils; -use satoru::event::event_utils; +use satoru::event::event_utils::{ + LogData, LogDataTrait, Felt252IntoU128, Felt252IntoContractAddress, ContractAddressDictValue, + I128252DictValue +}; +use satoru::utils::serializable_dict::{SerializableFelt252Dict, SerializableFelt252DictTrait}; use satoru::order::error::OrderError; use satoru::bank::bank::{IBankDispatcher, IBankDispatcherTrait}; use satoru::utils::span32::{Span32, DefaultSpan32}; use satoru::oracle::error::OracleError; #[inline(always)] -fn process_order(params: ExecuteOrderParams) -> event_utils::LogData { +fn process_order(params: ExecuteOrderParams) -> LogData { if (params.order.market.is_non_zero()) { panic(array![OrderError::UNEXPECTED_MARKET]); } @@ -44,23 +48,12 @@ fn process_order(params: ExecuteOrderParams) -> event_utils::LogData { } ); - let mut address_items: event_utils::AddressItems = Default::default(); - let mut uint_items: event_utils::UintItems = Default::default(); + let mut log_data: LogData = Default::default(); - address_items = - event_utils::set_item_address_items(address_items, 0, 'output_token', output_token); + log_data.address_dict.insert_single('output_token', output_token); + log_data.uint_dict.insert_single('output_amount', output_amount); - uint_items = event_utils::set_item_uint_items(uint_items, 0, 'output_amount', output_amount); - - event_utils::LogData { - address_items, - uint_items, - int_items: Default::default(), - bool_items: Default::default(), - felt252_items: Default::default(), - array_of_felt_items: Default::default(), - string_items: Default::default(), - } + log_data } diff --git a/src/utils/serializable_dict.cairo b/src/utils/serializable_dict.cairo new file mode 100644 index 00000000..e5baed05 --- /dev/null +++ b/src/utils/serializable_dict.cairo @@ -0,0 +1,310 @@ +use core::serde::Serde; +use core::array::SpanTrait; +use core::array::ArrayTrait; +use core::traits::Into; +use starknet::{get_caller_address, ContractAddress, contract_address_const}; +use traits::Default; +use dict::{Felt252DictTrait, Felt252Dict}; +use nullable::{nullable_from_box, match_nullable, FromNullableResult, Nullable}; + +use alexandria_data_structures::array_ext::ArrayTraitExt; + +/// +/// Item +/// +/// Enumeration used to store a value in a SerializableDict. +/// It allows to store either a simple value (Single) or a +/// Span & comes with utilities functions. +/// +#[derive(Drop, Copy)] +enum Item { + Single: T, + Span: Span +} + +#[generate_trait] +impl ItemImpl of ItemTrait { + fn is_single(self: @Item) -> bool { + match self { + Item::Single(v) => true, + Item::Span(arr) => false + } + } + + fn is_span(self: @Item) -> bool { + !self.is_single() + } + + fn len(self: @Item) -> usize { + match self { + Item::Single(v) => 1, + Item::Span(s) => (*s).len() + } + } + + fn unwrap_single>(self: @Item) -> T { + match self { + Item::Single(v) => (*v), + Item::Span(arr) => panic_with_felt252('should not be a span') + } + } + + fn unwrap_span(self: @Item) -> Span { + match self { + Item::Single(v) => panic_with_felt252('should not be single'), + Item::Span(arr) => *arr + } + } +} + +impl ItemPartialEq< + T, impl TCopy: Copy, impl TPartialEq: PartialEq, impl TDrop: Drop +> of PartialEq> { + fn eq(lhs: @Item, rhs: @Item) -> bool { + if lhs.is_single() && rhs.is_single() { + return lhs.unwrap_single() == rhs.unwrap_single(); + } else if lhs.is_span() && rhs.is_span() { + return lhs.unwrap_span() == rhs.unwrap_span(); + } + return false; + } + fn ne(lhs: @Item, rhs: @Item) -> bool { + if lhs.is_single() && rhs.is_single() { + return !(lhs.unwrap_single() == rhs.unwrap_single()); + } else if lhs.is_span() && rhs.is_span() { + return !(lhs.unwrap_span() == rhs.unwrap_span()); + } + return true; + } +} + +/// +/// SerializableFelt252Dict +/// +/// Wrapper around the Felt252Dict. +/// It behaves the same as a regular dict but has also a keys parameter +/// that keeps track of the keys registered. +/// This allows us to serialize & deserialize the struct, which is not +/// possible with a regular Felt252Dict. +/// The values are wrapped around an Item struct that allows to store +/// different types of data: a simple value or a span. +/// +#[derive(Default)] +struct SerializableFelt252Dict { + keys: Array, + values: Felt252Dict>> +} + +impl SerializableFelt252DictDestruct< + T, impl TDrop: Drop, impl TDefault: Felt252DictValue +> of Destruct> { + fn destruct(self: SerializableFelt252Dict) nopanic { + self.values.squash(); + self.keys.destruct(); + } +} + +trait SerializableFelt252DictTrait { + /// Creates a new SerializableFelt252Dict object. + fn new() -> SerializableFelt252Dict; + /// Adds an element. + fn insert_single(ref self: SerializableFelt252Dict, key: felt252, value: T); + /// Adds an array of elements. + fn insert_span(ref self: SerializableFelt252Dict, key: felt252, values: Span); + /// Gets an element. + fn get(ref self: SerializableFelt252Dict, key: felt252) -> Option>; + /// Checks if a key is in the dictionnary. + fn contains(self: @SerializableFelt252Dict, key: felt252) -> bool; + /// Number of keys in the dictionnary. + fn len(self: @SerializableFelt252Dict) -> usize; + /// Checks if a dictionnary is empty. + fn is_empty(self: @SerializableFelt252Dict) -> bool; + /// Serializes the dictionnary & return the result + fn serialize_into(ref self: SerializableFelt252Dict) -> Array; + /// Serializes the dictionnary into the provided output array + fn serialize(ref self: SerializableFelt252Dict, ref output: Array); + /// Deserializes the serialized array & return the dictionnary + fn deserialize(ref serialized: Span) -> Option>; +} + +impl SerializableFelt252DictTraitImpl< + T, + impl TDefault: Felt252DictValue, + impl TDrop: Drop, + impl TCopy: Copy, + impl FeltIntoT: Into, + impl TIntoFelt: Into, +> of SerializableFelt252DictTrait { + fn new() -> SerializableFelt252Dict { + SerializableFelt252Dict { keys: array![], values: Default::default() } + } + + fn insert_single(ref self: SerializableFelt252Dict, key: felt252, value: T) { + let value = Item::Single(value); + if !self.keys.contains(key) { + self.keys.append(key); + } + self.values.insert(key, nullable_from_box(BoxTrait::new(value))); + } + + fn insert_span(ref self: SerializableFelt252Dict, key: felt252, values: Span) { + let values = Item::Span(values); + if !self.keys.contains(key) { + self.keys.append(key); + } + self.values.insert(key, nullable_from_box(BoxTrait::new(values))); + } + + fn get(ref self: SerializableFelt252Dict, key: felt252) -> Option> { + match match_nullable(self.values.get(key)) { + FromNullableResult::Null(()) => Option::None, + FromNullableResult::NotNull(val) => Option::Some(val.unbox()), + } + } + + fn contains(self: @SerializableFelt252Dict, key: felt252) -> bool { + let mut keys: Span = self.keys.span(); + let mut contains_key: bool = false; + loop { + match keys.pop_front() { + Option::Some(value) => { + if *value == key { + contains_key = true; + break; + } + }, + Option::None => { + break; + }, + }; + }; + return contains_key; + } + + fn len(self: @SerializableFelt252Dict) -> usize { + self.keys.len() + } + + fn is_empty(self: @SerializableFelt252Dict) -> bool { + self.len() == 0 + } + + fn serialize_into(ref self: SerializableFelt252Dict) -> Array { + let mut serialized_data: Array = array![]; + self.serialize(ref serialized_data); + serialized_data + } + + // + // Serialization of an SerializableFelt252Dict + // + // An SerializableFelt252Dict is serialized as follow: + // [ KEY | NB_ELEMENTS | X | Y | ... | KEY | NB_ELEMENTS | X | ...] + // + // + // e.g. if we try to serialize this Dict: + // keys: [0, 1] + // values: { + // 0: 1, + // 1: [1, 2, 3] + // } + // + // will give: + // + // key: 0 key: 1 + // | ------ | ----------- | + // [0, 1, 1, 1, 3, 1, 2, 3] (Array) + // + fn serialize(ref self: SerializableFelt252Dict, ref output: Array) { + let mut keys: Span = self.keys.span(); + loop { + match keys.pop_front() { + Option::Some(key) => { + let value: Item = self.get(*key).expect('key should exist'); + match value { + Item::Single(v) => { + output.append(*key); // key + output.append(1); // len + output.append(v.into()); // value + }, + Item::Span(mut arr) => { + output.append(*key); // key + output.append(arr.len().into()); // len + loop { // append each values + match arr.pop_front() { + Option::Some(v) => { + output.append((*v).into()); + }, + Option::None => { + break; + } + }; + }; + }, + }; + }, + Option::None => { + break; + }, + }; + }; + } + + // + // Deserialization of an SerializableFelt252Dict + // + // An SerializableFelt252Dict is serialized as follow: + // [ KEY | NB_ELEMENTS | X | Y | ... | KEY | NB_ELEMENTS | X | ...] + // + fn deserialize(ref serialized: Span) -> Option> { + let mut d: SerializableFelt252Dict = SerializableFelt252Dict { + keys: array![], values: Default::default() + }; + loop { + // Try to retrive the next key + match serialized.pop_front() { + Option::Some(key) => { + // Key found; try to retrieved the size of elements + match serialized.pop_front() { + Option::Some(size) => { + // If only one element, insert it & quit + if ((*size) == 1) { + let value: T = match serialized.pop_front() { + Option::Some(value) => (*value).into(), + Option::None => panic_with_felt252('err getting value') + }; + let value: Item = Item::Single(value); + d.keys.append(*key); + d.values.insert(*key, nullable_from_box(BoxTrait::new(value))); + continue; + }; + // Else append all elements into an array ... + let mut arr_size: felt252 = *size; + let mut arr_values: Array = array![]; + loop { + if (arr_size) == 0 { + break; + }; + let value: T = match serialized.pop_front() { + Option::Some(value) => (*value).into(), + Option::None => panic_with_felt252('err getting value') + }; + arr_values.append(value); + arr_size -= 1; + }; + // ... & insert it + let values: Item = Item::Span(arr_values.span()); + d.keys.append(*key); + d.values.insert(*key, nullable_from_box(BoxTrait::new(values))); + }, + Option::None => panic_with_felt252('err getting size') + } + }, + Option::None => { + break; + }, + }; + }; + Option::Some(d) + } +} diff --git a/tests/callback/test_callback_utils.cairo b/tests/callback/test_callback_utils.cairo index 5eeff2fa..b1a8d216 100644 --- a/tests/callback/test_callback_utils.cairo +++ b/tests/callback/test_callback_utils.cairo @@ -5,7 +5,7 @@ use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; use satoru::data::data_store::IDataStoreDispatcherTrait; use satoru::data::keys; use satoru::deposit::deposit::Deposit; -use satoru::event::event_utils::LogData; +use satoru::event::event_utils::{LogData, LogDataTrait}; use satoru::callback::callback_utils::{ validate_callback_gas_limit, set_saved_callback_contract, get_saved_callback_contract, after_deposit_execution @@ -57,7 +57,7 @@ fn given_normal_conditions_when_callback_contract_functions_then_works() { let (_, _, data_store) = setup(); let mut deposit: Deposit = Default::default(); - let log_data: LogData = Default::default(); + let mut log_data: LogData = Default::default(); let (_, event_emitter) = setup_event_emitter(); let callback_mock = deploy_callback_mock(); diff --git a/tests/event/test_event_utils.cairo b/tests/event/test_event_utils.cairo new file mode 100644 index 00000000..6fa74377 --- /dev/null +++ b/tests/event/test_event_utils.cairo @@ -0,0 +1,169 @@ +//! Test file for `src/event/event_utils.cairo`. + +// ************************************************************************* +// IMPORTS +// ************************************************************************* +use starknet::{ + get_caller_address, ContractAddress, Felt252TryIntoContractAddress, ContractAddressIntoFelt252, + contract_address_const +}; +use satoru::event::event_utils::{ + Felt252IntoBool, Felt252IntoU128, Felt252IntoI128, Felt252IntoContractAddress, I128252DictValue, + ContractAddressDictValue, LogData, LogDataTrait +}; +use satoru::utils::traits::{ContractAddressDefault}; +use traits::Default; +use satoru::utils::serializable_dict::{ + Item, ItemTrait, SerializableFelt252Dict, SerializableFelt252DictTrait, +}; + +// ********************************************************************************************* +// * TEST LOGIC * +// ********************************************************************************************* + +#[test] +fn test_log_data_default() { + let mut log_data: LogData = Default::default(); + + // try to add things + log_data.address_dict.insert_single('test', contract_address_const::<0>()); + log_data.uint_dict.insert_single('test', 12_u128); + + // assert results OK + let addr_item = log_data.address_dict.get('test').expect('key not found'); + let addr_value = addr_item.unwrap_single(); + assert(addr_value == contract_address_const::<0>(), 'addr value wrong'); + + let uint_item = log_data.uint_dict.get('test').expect('key not found'); + let uint_value = uint_item.unwrap_single(); + assert(uint_value == 12_u128, 'uint value wrong'); +} + +#[test] +fn test_log_data_default_each() { + let mut log_data: LogData = LogData { + address_dict: Default::default(), + uint_dict: Default::default(), + int_dict: Default::default(), + bool_dict: Default::default(), + felt252_dict: Default::default(), + string_dict: Default::default() + }; + + // try to add things + log_data.address_dict.insert_single('test', contract_address_const::<0>()); + log_data.uint_dict.insert_single('test', 12_u128); + + // assert results OK + let addr_item = log_data.address_dict.get('test').expect('key not found'); + let addr_value = addr_item.unwrap_single(); + assert(addr_value == contract_address_const::<0>(), 'addr value wrong'); + + let uint_item = log_data.uint_dict.get('test').expect('key not found'); + let uint_value = uint_item.unwrap_single(); + assert(uint_value == 12_u128, 'uint value wrong'); +} + +#[test] +fn test_log_data_multiple_types() { + let mut log_data: LogData = Default::default(); + + let arr_to_add: Array = array![ + contract_address_const::<'cairo'>(), + contract_address_const::<'starknet'>(), + contract_address_const::<'rust'>() + ]; + + // try to add unique + log_data.address_dict.insert_single('test', contract_address_const::<0>()); + log_data.address_dict.insert_span('test_arr', arr_to_add.span()); + + // assert results OK + let addr_item = log_data.address_dict.get('test').expect('key not found'); + let addr_value = addr_item.unwrap_single(); + assert(addr_value == contract_address_const::<0>(), 'addr value wrong'); + + let addr_span_item: Item = log_data + .address_dict + .get('test_arr') + .expect('key should be in dict'); + let out_span: Span = addr_span_item.unwrap_span(); + assert(out_span.at(0) == arr_to_add.at(0), 'wrong at idx 0'); + assert(out_span.at(1) == arr_to_add.at(1), 'wrong at idx 1'); + assert(out_span.at(2) == arr_to_add.at(2), 'wrong at idx 2'); +} + +#[test] +fn test_log_data_serialization() { + let mut log_data: LogData = Default::default(); + + log_data.address_dict.insert_single('addr_test', contract_address_const::<42>()); + log_data.bool_dict.insert_single('bool_test', false); + log_data.felt252_dict.insert_single('felt_test', 1); + log_data.felt252_dict.insert_single('felt_test_two', 2); + log_data.string_dict.insert_single('string_test', 'hello world'); + log_data + .string_dict + .insert_span('string_arr_test', array!['hello', 'world', 'from', 'starknet'].span()); + + // serialize the data + let mut serialized_data = log_data.serialize_into().span(); + + // deserialize + let mut d_log_data: LogData = LogDataTrait::deserialize(ref serialized_data) + .expect('err while deserializing'); + + // Check the values inserted before + // addr dict + let mut expected_dict = log_data.address_dict; + let mut out_dict = d_log_data.address_dict; + assert_same_single_value_for_dicts(ref expected_dict, ref out_dict, 'addr_test'); + + // bool dict + let mut expected_dict = log_data.bool_dict; + let mut out_dict = d_log_data.bool_dict; + assert_same_single_value_for_dicts(ref expected_dict, ref out_dict, 'bool_test'); + + // felt252 dict + let mut expected_dict = log_data.felt252_dict; + let mut out_dict = d_log_data.felt252_dict; + assert_same_single_value_for_dicts(ref expected_dict, ref out_dict, 'felt_test'); + assert_same_single_value_for_dicts(ref expected_dict, ref out_dict, 'felt_test_two'); + + // string dict + assert(d_log_data.string_dict.contains('string_arr_test'), 'key not found'); + let v: Item = d_log_data.string_dict.get('string_arr_test').unwrap(); + let span_strings: Span = v.unwrap_span(); + assert(span_strings.len() == 4, 'err span len'); + assert(span_strings.at(0) == @'hello', 'err idx 0'); + assert(span_strings.at(1) == @'world', 'err idx 1'); + assert(span_strings.at(2) == @'from', 'err idx 2'); + assert(span_strings.at(3) == @'starknet', 'err idx 3'); +} + + +// ********************************************************************************************* +// * UTILITIES * +// ********************************************************************************************* + +use debug::PrintTrait; + +fn assert_same_single_value_for_dicts< + T, + impl TDefault: Felt252DictValue, + impl TDrop: Drop, + impl TCopy: Copy, + impl FeltIntoT: Into, + impl TIntoFelt: Into, + impl TPartialEq: PartialEq, +>( + ref lhs: SerializableFelt252Dict, ref rhs: SerializableFelt252Dict, key: felt252 +) { + assert(lhs.contains(key), 'key not found: lhs'); + assert(rhs.contains(key), 'key not found: rhs'); + + let lhs_value: Item = lhs.get(key).unwrap(); + let rhs_value: Item = rhs.get(key).unwrap(); + + assert(lhs_value == rhs_value, 'err value'); +} diff --git a/tests/lib.cairo b/tests/lib.cairo index 2a77e80f..27c244a2 100644 --- a/tests/lib.cairo +++ b/tests/lib.cairo @@ -39,6 +39,7 @@ mod event { mod test_swap_events_emitted; mod test_timelock_events_emitted; mod test_withdrawal_events_emitted; + mod test_event_utils; } mod exchange { mod test_liquidation_handler; @@ -106,6 +107,7 @@ mod utils { mod test_starknet_utils; mod test_u128_mask; mod test_i128; + mod test_serializable_dict; } mod withdrawal { mod test_withdrawal_vault; diff --git a/tests/utils/test_serializable_dict.cairo b/tests/utils/test_serializable_dict.cairo new file mode 100644 index 00000000..64288d43 --- /dev/null +++ b/tests/utils/test_serializable_dict.cairo @@ -0,0 +1,121 @@ +//! Test file for `src/utils/serializable_dict.cairo`. + +// ************************************************************************* +// IMPORTS +// ************************************************************************* +// Core lib imports. +use serde::Serde; +use starknet::{ + get_caller_address, ContractAddress, Felt252TryIntoContractAddress, ContractAddressIntoFelt252, + contract_address_const +}; +use array::ArrayTrait; +use array::SpanTrait; +use traits::Default; +use alexandria_data_structures::array_ext::ArrayTraitExt; + +// Local imports. +use satoru::utils::traits::ContractAddressDefault; +use satoru::event::event_utils::{ + Felt252IntoBool, Felt252IntoU128, Felt252IntoI128, Felt252IntoContractAddress, I128252DictValue, + ContractAddressDictValue +}; +use satoru::utils::serializable_dict::{ + Item, ItemTrait, SerializableFelt252Dict, SerializableFelt252DictTrait, + SerializableFelt252DictTraitImpl +}; + +// ********************************************************************************************* +// * TEST LOGIC * +// ********************************************************************************************* + +/// Item tests + +#[test] +fn test_item_single() { + let item: Item = Item::Single(8); + + assert(item.is_single() == true, 'item should be single'); + assert(item.is_span() == false, 'item shouldnt be a span'); + assert(item.len() == 1, 'item len not 1'); +} + +#[test] +fn test_item_span() { + let arr: Array = array![1, 2, 3, 4, 5]; + let expected_len: usize = arr.len(); + + let item: Item = Item::Span(arr.span()); + + assert(item.is_span() == true, 'item should be a span'); + assert(item.is_single() == false, 'item shouldnt be single'); + assert(item.len() == expected_len, 'incorrect len'); +} + +// SerializableDict tests + +#[test] +fn test_serializable_dict_insert_single() { + let mut dict: SerializableFelt252Dict = SerializableFelt252DictTrait::new(); + + let key: felt252 = 'starknet'; + let expected_value: u128 = 42; + + dict.insert_single(key, expected_value); + + let retrieved_item: Item = dict.get(key).expect('key should be in dict'); + let out_value: u128 = retrieved_item.unwrap_single(); + + assert(out_value == expected_value, 'wrong value'); +} + +#[test] +fn test_serializable_dict_insert_span() { + let mut dict: SerializableFelt252Dict = SerializableFelt252DictTrait::new(); + + let key: felt252 = 'starknet'; + let expected_array: Array = array![1, 2, 3]; + + dict.insert_span(key, expected_array.span()); + + let retrieved_item: Item = dict.get(key).expect('key should be in dict'); + let out_span: Span = retrieved_item.unwrap_span(); + + assert(dict.contains(key), 'key should be in dict'); + assert(out_span.at(0) == expected_array.at(0), 'wrong at idx 0'); + assert(out_span.at(1) == expected_array.at(1), 'wrong at idx 1'); + assert(out_span.at(2) == expected_array.at(2), 'wrong at idx 2'); +} + +#[test] +fn test_serializable_dict_serialize() { + let mut dict: SerializableFelt252Dict = SerializableFelt252DictTrait::new(); + + let expected_value: u128 = 42; + let expected_array: Array = array![1, 2, 3]; + + dict.insert_single('test', expected_value); + dict.insert_span('test_span', expected_array.span()); + + let serialized: Array = dict.serialize_into(); + + let mut span_serialized: Span = serialized.span(); + let mut deserialized_dict: SerializableFelt252Dict = + match SerializableFelt252DictTrait::::deserialize(ref span_serialized) { + Option::Some(d) => d, + Option::None => panic_with_felt252('err while recreating d') + }; + + assert(dict.contains('test'), 'key should be in dict'); + let retrieved_item: Item = dict.get('test').expect('key should be in dict'); + let out_value: u128 = retrieved_item.unwrap_single(); + + assert(dict.contains('test_span'), 'key should be in dict'); + let retrieved_item: Item = deserialized_dict + .get('test_span') + .expect('key should be in dict'); + let out_span: Span = retrieved_item.unwrap_span(); + assert(out_span.at(0) == expected_array.at(0), 'wrong at idx 0'); + assert(out_span.at(1) == expected_array.at(1), 'wrong at idx 1'); + assert(out_span.at(2) == expected_array.at(2), 'wrong at idx 2'); +}