diff --git a/src/lib.cairo b/src/lib.cairo index 9eab445..96716da 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -39,6 +39,7 @@ mod randomness { mod randomness; } mod lp_pricer { + mod concat; mod lp_pricer; } #[cfg(test)] diff --git a/src/lp_pricer/concat.cairo b/src/lp_pricer/concat.cairo new file mode 100644 index 0000000..597d02a --- /dev/null +++ b/src/lp_pricer/concat.cairo @@ -0,0 +1,348 @@ +// Source: +// https://github.com/underware-gg/underdark/blob/258c3ca96a728a605b70b21a3fa697d290d31409/dojo/src/utils/string.cairo#L4 + +const U8_ONE_LEFT: u8 = 0x80; +const U16_ONE_LEFT: u16 = 0x8000; +const U32_ONE_LEFT: u32 = 0x80000000; +const U64_ONE_LEFT: u64 = 0x8000000000000000; +const U128_ONE_LEFT: u128 = 0x80000000000000000000000000000000; +const U256_ONE_LEFT: u256 = 0x8000000000000000000000000000000000000000000000000000000000000000; + +trait Bitwise { + fn bit(n: usize) -> T; + fn set(x: T, n: usize) -> T; + fn unset(x: T, n: usize) -> T; + fn shl(x: T, n: usize) -> T; + fn shr(x: T, n: usize) -> T; + fn is_set(x: T, n: usize) -> bool; + fn count_bits(x: T) -> usize; +} + +impl U8Bitwise of Bitwise { + fn bit(n: usize) -> u8 { + if n == 0 { + return 0b00000001; + } + if n == 1 { + return 0b00000010; + } + if n == 2 { + return 0b00000100; + } + if n == 3 { + return 0b00001000; + } + if n == 4 { + return 0b00010000; + } + if n == 5 { + return 0b00100000; + } + if n == 6 { + return 0b01000000; + } + if n == 7 { + return 0b10000000; + } + 0 + } + #[inline(always)] + fn set(x: u8, n: usize) -> u8 { + x | U8Bitwise::bit(n) + } + #[inline(always)] + fn unset(x: u8, n: usize) -> u8 { + x & ~U8Bitwise::bit(n) + } + #[inline(always)] + fn shl(x: u8, n: usize) -> u8 { + x * U8Bitwise::bit(n) + } + #[inline(always)] + fn shr(x: u8, n: usize) -> u8 { + if (n < 8) { + return (x / U8Bitwise::bit(n)); + } + 0 + } + #[inline(always)] + fn is_set(x: u8, n: usize) -> bool { + ((U8Bitwise::shr(x, n) & 1) != 0) + } + fn count_bits(x: u8) -> usize { + let mut result: usize = 0; + let mut bit: u8 = U8_ONE_LEFT; + loop { + if (x & bit > 0) { + result += 1; + }; + if (bit == 0x1) { + break; + } + bit /= 2; + }; + result + } +} + +impl U16Bitwise of Bitwise { + fn bit(n: usize) -> u16 { + if n < 8 { + return U8Bitwise::bit(n).into(); + } + if n < 16 { + return U8Bitwise::bit(n - 8).into() * 0x100; + } + 0 + } + #[inline(always)] + fn set(x: u16, n: usize) -> u16 { + x | U16Bitwise::bit(n) + } + #[inline(always)] + fn unset(x: u16, n: usize) -> u16 { + x & ~U16Bitwise::bit(n) + } + #[inline(always)] + fn shl(x: u16, n: usize) -> u16 { + x * U16Bitwise::bit(n) + } + #[inline(always)] + fn shr(x: u16, n: usize) -> u16 { + if (n < 16) { + return (x / U16Bitwise::bit(n)); + } + 0 + } + #[inline(always)] + fn is_set(x: u16, n: usize) -> bool { + ((U16Bitwise::shr(x, n) & 1) != 0) + } + fn count_bits(x: u16) -> usize { + let mut result: usize = 0; + let mut bit: u16 = U16_ONE_LEFT; + loop { + if (x & bit > 0) { + result += 1; + }; + if (bit == 0x1) { + break; + } + bit /= 2; + }; + result + } +} + +impl U32Bitwise of Bitwise { + fn bit(n: usize) -> u32 { + if n < 16 { + return U16Bitwise::bit(n).into(); + } + if n < 32 { + return U16Bitwise::bit(n - 16).into() * 0x10000; + } + 0 + } + #[inline(always)] + fn set(x: u32, n: usize) -> u32 { + x | U32Bitwise::bit(n) + } + #[inline(always)] + fn unset(x: u32, n: usize) -> u32 { + x & ~U32Bitwise::bit(n) + } + #[inline(always)] + fn shl(x: u32, n: usize) -> u32 { + x * U32Bitwise::bit(n) + } + #[inline(always)] + fn shr(x: u32, n: usize) -> u32 { + if (n < 32) { + return (x / U32Bitwise::bit(n)); + } + 0 + } + #[inline(always)] + fn is_set(x: u32, n: usize) -> bool { + ((U32Bitwise::shr(x, n) & 1) != 0) + } + fn count_bits(x: u32) -> usize { + let mut result: usize = 0; + let mut bit: u32 = U32_ONE_LEFT; + loop { + if (x & bit > 0) { + result += 1; + }; + if (bit == 0x1) { + break; + } + bit /= 2; + }; + result + } +} + +impl U64Bitwise of Bitwise { + fn bit(n: usize) -> u64 { + if n < 32 { + return U32Bitwise::bit(n).into(); + } + if n < 64 { + return U32Bitwise::bit(n - 32).into() * 0x100000000; + } + 0 + } + #[inline(always)] + fn set(x: u64, n: usize) -> u64 { + x | U64Bitwise::bit(n) + } + #[inline(always)] + fn unset(x: u64, n: usize) -> u64 { + x & ~U64Bitwise::bit(n) + } + #[inline(always)] + fn shl(x: u64, n: usize) -> u64 { + x * U64Bitwise::bit(n) + } + #[inline(always)] + fn shr(x: u64, n: usize) -> u64 { + if (n < 64) { + return (x / U64Bitwise::bit(n)); + } + 0 + } + #[inline(always)] + fn is_set(x: u64, n: usize) -> bool { + ((U64Bitwise::shr(x, n) & 1) != 0) + } + fn count_bits(x: u64) -> usize { + let mut result: usize = 0; + let mut bit: u64 = U64_ONE_LEFT; + loop { + if (x & bit > 0) { + result += 1; + }; + if (bit == 0x1) { + break; + } + bit /= 2; + }; + result + } +} + +impl U128Bitwise of Bitwise { + fn bit(n: usize) -> u128 { + if n < 64 { + return U64Bitwise::bit(n).into(); + } + if n < 128 { + return U64Bitwise::bit(n - 64).into() * 0x10000000000000000; + } + 0 + } + #[inline(always)] + fn set(x: u128, n: usize) -> u128 { + x | U128Bitwise::bit(n) + } + #[inline(always)] + fn unset(x: u128, n: usize) -> u128 { + x & ~U128Bitwise::bit(n) + } + #[inline(always)] + fn shl(x: u128, n: usize) -> u128 { + x * U128Bitwise::bit(n) + } + #[inline(always)] + fn shr(x: u128, n: usize) -> u128 { + if (n < 128) { + return (x / U128Bitwise::bit(n)); + } + 0 + } + #[inline(always)] + fn is_set(x: u128, n: usize) -> bool { + ((U128Bitwise::shr(x, n) & 1) != 0) + } + fn count_bits(x: u128) -> usize { + let mut result: usize = 0; + let mut bit: u128 = U128_ONE_LEFT; + loop { + if (x & bit > 0) { + result += 1; + }; + if (bit == 0x1) { + break; + } + bit /= 2; + }; + result + } +} + +impl U256Bitwise of Bitwise { + fn bit(n: usize) -> u256 { + if n < 128 { + return u256 { low: U128Bitwise::bit(n), high: 0x0 }; + } + if n < 256 { + return u256 { low: 0x0, high: U128Bitwise::bit(n - 128) }; + } + 0 + } + #[inline(always)] + fn set(x: u256, n: usize) -> u256 { + x | U256Bitwise::bit(n) + } + #[inline(always)] + fn unset(x: u256, n: usize) -> u256 { + x & ~U256Bitwise::bit(n) + } + #[inline(always)] + fn shl(x: u256, n: usize) -> u256 { + x * U256Bitwise::bit(n) + } + #[inline(always)] + fn shr(x: u256, n: usize) -> u256 { + if (n < 256) { + return (x / U256Bitwise::bit(n)); + } + 0 + } + #[inline(always)] + fn is_set(x: u256, n: usize) -> bool { + ((U256Bitwise::shr(x, n) & 1) != 0) + } + fn count_bits(x: u256) -> usize { + let mut result: usize = 0; + let mut bit: u256 = U256_ONE_LEFT; + loop { + if (x & bit > 0) { + result += 1; + }; + if (bit == 0x1) { + break; + } + bit /= 2; + }; + result + } +} + +fn concat(left: felt252, right: felt252) -> felt252 { + let _left: u256 = left.into(); + let _right: u256 = right.into(); + let mut offset: usize = 0; + let mut i: usize = 0; + loop { + if (i == 256) { + break; + } + if (_right & U256Bitwise::shl(0xff, i) != 0) { + offset = i + 8; + } + i += 8; + }; + (_right | U256Bitwise::shl(_left, offset)).try_into().unwrap() +} diff --git a/src/lp_pricer/lp_pricer.cairo b/src/lp_pricer/lp_pricer.cairo index c2bb25e..72726af 100644 --- a/src/lp_pricer/lp_pricer.cairo +++ b/src/lp_pricer/lp_pricer.cairo @@ -2,26 +2,16 @@ use starknet::ContractAddress; #[derive(Copy, Drop, Serde, Debug)] struct PoolInfo { + address: ContractAddress, name: felt252, symbol: felt252, - address: ContractAddress, decimals: u8, total_supply: u256, } #[starknet::interface] trait IERC20 { - fn name(self: @TContractState) -> felt252; fn symbol(self: @TContractState) -> felt252; - fn decimals(self: @TContractState) -> u8; - fn total_supply(self: @TContractState) -> u256; - fn balance_of(self: @TContractState, account: ContractAddress) -> u256; - fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool; } #[starknet::interface] @@ -70,11 +60,14 @@ mod LpPricer { use traits::Into; use traits::TryInto; use pragma::admin::admin::Ownable; - use pragma::oracle::oracle::{IOracleABIDispatcher, IOracleABIDispatcherTrait}; + use pragma::oracle::oracle::{IOracleABIDispatcher, IOracleABIDispatcherTrait, DataType}; use super::{ PoolInfo, ILpPricer, IPoolDispatcher, IPoolDispatcherTrait, IERC20Dispatcher, IERC20DispatcherTrait }; + use pragma::lp_pricer::concat::concat; + + const USD_PAIR_SUFFIX: felt252 = '/USD'; // ================== ERRORS ================== @@ -155,14 +148,15 @@ mod LpPricer { let pool = self.supported_pools.read(pool_address); let total_supply = pool.total_supply(); - // [Effect] Get the addresses & reserves + // [Effect] Get the addresses, reserves & symbols let (token_a_address, token_b_address) = get_tokens_addresses(pool); let (token_a_reserve, token_b_reserve) = pool.get_reserves(); + let (token_a_id, token_b_id) = get_tokens_symbols(token_a_address, token_b_address); // [Effect] Get token prices let oracle = self.oracle.read(); - let token_a_price = get_price_in_usd(oracle, token_a_address); - let token_b_price = get_price_in_usd(oracle, token_b_address); + let token_a_price = get_currency_price_in_usd(oracle, token_a_id); + let token_b_price = get_currency_price_in_usd(oracle, token_b_id); (token_a_reserve * token_a_price + token_b_reserve * token_b_price) / total_supply } @@ -180,8 +174,9 @@ mod LpPricer { let oracle = self.oracle.read(); let pool = IPoolDispatcher { contract_address: pool_address }; let (token_a_address, token_b_address) = get_tokens_addresses(pool); - assert(currency_is_supported(oracle, token_a_address), errors::UNSUPPORTED_CURRENCY); - assert(currency_is_supported(oracle, token_b_address), errors::UNSUPPORTED_CURRENCY); + let (token_a_id, token_b_id) = get_tokens_symbols(token_a_address, token_b_address); + assert(currency_is_supported(oracle, token_a_id), errors::UNSUPPORTED_CURRENCY); + assert(currency_is_supported(oracle, token_b_id), errors::UNSUPPORTED_CURRENCY); // [Effect] Add the pool to the storage self.supported_pools.write(pool_address, pool); @@ -237,9 +232,9 @@ mod LpPricer { // [Interaction] Return the pool informations PoolInfo { + address: pool.contract_address, name: pool.name(), symbol: pool.symbol(), - address: pool.contract_address, decimals: pool.decimals(), total_supply: pool.total_supply() } @@ -306,16 +301,26 @@ mod LpPricer { (pool.token_0(), pool.token_1()) } + /// Retrieves the token symbols from the underlying currencies. + fn get_tokens_symbols( + token_a_address: ContractAddress, token_b_address: ContractAddress + ) -> (felt252, felt252) { + let token_a = IERC20Dispatcher { contract_address: token_a_address }; + let token_b = IERC20Dispatcher { contract_address: token_b_address }; + (token_a.symbol(), token_b.symbol()) + } + /// Returns true if the currency is supported by Pragma, else false. - fn currency_is_supported( - oracle: IOracleABIDispatcher, currency_address: ContractAddress - ) -> bool { - let currency = IERC20Dispatcher { contract_address: currency_address }; - oracle.get_currency(currency.name()).id != 0 + fn currency_is_supported(oracle: IOracleABIDispatcher, currency_id: felt252) -> bool { + oracle.get_currency(currency_id).id != 0 } - /// Returns the price in USD for a currency by fetching the Pragma Oracle. - fn get_price_in_usd(oracle: IOracleABIDispatcher, currency_address: ContractAddress) -> u256 { - 0 + /// Returns the price in USD for a currency by fetching it from the Pragma Oracle. + fn get_currency_price_in_usd(oracle: IOracleABIDispatcher, currency_id: felt252) -> u256 { + let pair_id = concat(currency_id, USD_PAIR_SUFFIX); + let data_type = DataType::SpotEntry(pair_id); + let data = oracle.get_data_median(data_type); + + data.price.into() } }