diff --git a/.snfoundry_cache/.prev_tests_failed b/.snfoundry_cache/.prev_tests_failed deleted file mode 100644 index e69de29..0000000 diff --git a/src/contracts/mocks.cairo b/src/contracts/mocks.cairo index 0eac7ca..fd55be4 100644 --- a/src/contracts/mocks.cairo +++ b/src/contracts/mocks.cairo @@ -1 +1,3 @@ pub(crate) mod store_packing_contract; +pub(crate) mod erc20_felt252; +pub(crate) mod erc20_bytearray; diff --git a/src/contracts/mocks/erc20_bytearray.cairo b/src/contracts/mocks/erc20_bytearray.cairo new file mode 100644 index 0000000..38c93fd --- /dev/null +++ b/src/contracts/mocks/erc20_bytearray.cairo @@ -0,0 +1,34 @@ +#[starknet::interface] +pub trait IERC20ByteArray { + fn name(self: @TContractState) -> ByteArray; + fn symbol(self: @TContractState) -> ByteArray; +} + +#[starknet::contract] +pub mod ERC20ByteArray { + use super::IERC20ByteArray; + use starknet::ContractAddress; + + #[storage] + struct Storage { + name: ByteArray, + symbol: ByteArray, + } + + #[constructor] + fn constructor(ref self: ContractState, name: ByteArray, symbol: ByteArray) { + self.name.write(name); + self.symbol.write(symbol); + } + + #[abi(embed_v0)] + impl ERC20ByteArray of IERC20ByteArray { + fn name(self: @ContractState) -> ByteArray { + self.name.read() + } + + fn symbol(self: @ContractState) -> ByteArray { + self.symbol.read() + } + } +} diff --git a/src/contracts/mocks/erc20_felt252.cairo b/src/contracts/mocks/erc20_felt252.cairo new file mode 100644 index 0000000..c5692b3 --- /dev/null +++ b/src/contracts/mocks/erc20_felt252.cairo @@ -0,0 +1,34 @@ +#[starknet::interface] +pub trait IERC20Felt252 { + fn name(self: @TContractState) -> felt252; + fn symbol(self: @TContractState) -> felt252; +} + +#[starknet::contract] +pub mod ERC20Felt252 { + use super::IERC20Felt252; + use starknet::ContractAddress; + + #[storage] + struct Storage { + name: felt252, + symbol: felt252, + } + + #[constructor] + fn constructor(ref self: ContractState, name: felt252, symbol: felt252) { + self.name.write(name); + self.symbol.write(symbol); + } + + #[abi(embed_v0)] + impl ERC20Felt252 of IERC20Felt252 { + fn name(self: @ContractState) -> felt252 { + self.name.read() + } + + fn symbol(self: @ContractState) -> felt252 { + self.symbol.read() + } + } +} diff --git a/src/contracts/strategy.cairo b/src/contracts/strategy.cairo index 9f810d4..cadaaec 100644 --- a/src/contracts/strategy.cairo +++ b/src/contracts/strategy.cairo @@ -10,12 +10,17 @@ pub mod ReversionStrategy { use starknet::syscalls::{replace_class_syscall, deploy_syscall}; // Local imports. - use haiko_strategy_reversion::libraries::{trend_math, store_packing::StrategyStateStorePacking}; + use haiko_strategy_reversion::libraries::{ + trend_math, store_packing::StrategyStateStorePacking, erc20_versioned_call + }; use haiko_strategy_reversion::types::{Trend, StrategyState}; use haiko_strategy_reversion::interfaces::IReversionStrategy::IReversionStrategy; use haiko_strategy_reversion::interfaces::IVaultToken::{ IVaultTokenDispatcher, IVaultTokenDispatcherTrait }; + use haiko_strategy_reversion::interfaces::IERC20Metadata::{ + IERC20MetadataFelt252Dispatcher, IERC20MetadataFelt252DispatcherTrait, + }; // Haiko imports. use haiko_lib::{id, math::{math, price_math, liquidity_math, fee_math}}; @@ -536,10 +541,8 @@ pub mod ReversionStrategy { self.strategy_state.write(market_id, state); // Deploy token to keep track of strategy shares. - let base_symbol = ERC20ABIDispatcher { contract_address: market_info.base_token } - .symbol(); - let quote_symbol = ERC20ABIDispatcher { contract_address: market_info.quote_token } - .symbol(); + let base_symbol = erc20_versioned_call::get_symbol(market_info.base_token); + let quote_symbol = erc20_versioned_call::get_symbol(market_info.quote_token); let name: ByteArray = format!( "Haiko {} {}-{}", self.name.read(), base_symbol, quote_symbol ); diff --git a/src/interfaces.cairo b/src/interfaces.cairo index c529ab4..b368997 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -1,2 +1,3 @@ pub mod IReversionStrategy; pub mod IVaultToken; +pub mod IERC20Metadata; diff --git a/src/interfaces/IERC20Metadata.cairo b/src/interfaces/IERC20Metadata.cairo new file mode 100644 index 0000000..650c272 --- /dev/null +++ b/src/interfaces/IERC20Metadata.cairo @@ -0,0 +1,11 @@ +#[starknet::interface] +pub trait IERC20MetadataFelt252 { + fn name(self: @TContractState) -> felt252; + fn symbol(self: @TContractState) -> felt252; +} + +#[starknet::interface] +pub trait IERC20MetadataByteArray { + fn name(self: @TContractState) -> ByteArray; + fn symbol(self: @TContractState) -> ByteArray; +} diff --git a/src/libraries.cairo b/src/libraries.cairo index 2b65689..2b71418 100644 --- a/src/libraries.cairo +++ b/src/libraries.cairo @@ -1,2 +1,3 @@ pub mod trend_math; +pub mod erc20_versioned_call; pub(crate) mod store_packing; diff --git a/src/libraries/erc20_versioned_call.cairo b/src/libraries/erc20_versioned_call.cairo new file mode 100644 index 0000000..220d54e --- /dev/null +++ b/src/libraries/erc20_versioned_call.cairo @@ -0,0 +1,60 @@ +// Core lib imports. +use starknet::ContractAddress; +use starknet::syscalls::{replace_class_syscall, deploy_syscall, call_contract_syscall}; + +// Local imports. +use haiko_strategy_reversion::interfaces::IERC20Metadata::{ + IERC20MetadataFelt252Dispatcher, IERC20MetadataFelt252DispatcherTrait +}; + +// Fetch the symbol of an ERC20 token. +// Older ERC20 tokens use `felt252` symbols, whereas newer ones use `ByteArray`. +// This function examines the length of the returned array from `call_contract_syscall` +// to handle both cases. +// +// # Arguments +// * `contract_address` - address of ERC20 token +// * `selector` - erc20 entry point selector +pub fn get_symbol(contract_address: ContractAddress) -> ByteArray { + let result = call_contract_syscall( + contract_address, selector!("symbol"), ArrayTrait::::new().span() + ) + .unwrap(); + let length = result.len(); + let mut name: ByteArray = ""; + // Switch between cases based on the length of the result array. + // If the length is 1, then the symbol is a `felt252`. + // If the length is greater than 1, then the symbol is a `ByteArray`. + if length == 1 { + let mut byte_array: ByteArray = ""; + byte_array.append_word(*result.at(0), 31); + let mut i = 0; + loop { + if i == byte_array.len() { + break; + } + let byte = byte_array.at(i).unwrap(); + if byte != 0 { + name.append_byte(byte); + } + i += 1; + }; + } else { + let pending_word_len: u32 = (*result.at(length - 1)).try_into().unwrap(); + let mut i = 1; + loop { + if i == length - 1 { + break; + } + let word = *result.at(i); + let word_len = if i == length - 2 { + pending_word_len + } else { + 31 + }; + name.append_word(word, word_len); + i += 1; + }; + } + name +} diff --git a/src/tests.cairo b/src/tests.cairo index 24149fa..81cdcc9 100644 --- a/src/tests.cairo +++ b/src/tests.cairo @@ -1,3 +1,4 @@ pub(crate) mod helpers; pub(crate) mod test_strategy; pub(crate) mod test_store_packing; +pub(crate) mod test_erc20_interface; diff --git a/src/tests/test_erc20_interface.cairo b/src/tests/test_erc20_interface.cairo new file mode 100644 index 0000000..8dfcb4d --- /dev/null +++ b/src/tests/test_erc20_interface.cairo @@ -0,0 +1,44 @@ +// Core lib imports. +use starknet::ContractAddress; +use starknet::syscalls::{deploy_syscall, call_contract_syscall}; +use core::to_byte_array::{FormatAsByteArray, AppendFormattedToByteArray}; + +// Local imports. +use haiko_strategy_reversion::contracts::mocks::{ + erc20_bytearray::{IERC20ByteArrayDispatcher, IERC20ByteArrayDispatcherTrait}, + erc20_felt252::{IERC20Felt252Dispatcher, IERC20Felt252DispatcherTrait}, +}; +use haiko_strategy_reversion::libraries::erc20_versioned_call; + +// External imports. +use snforge_std::{declare, ContractClassTrait}; +use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + +#[test] +fn test_erc20_bytearray() { + let erc20_class = declare("ERC20ByteArray"); + let name: ByteArray = "Mock"; + let symbol: ByteArray = "MOCK"; + let mut calldata = array![]; + name.serialize(ref calldata); + symbol.serialize(ref calldata); + let contract_address = erc20_class.deploy(@calldata).unwrap(); + let result = erc20_versioned_call::get_symbol(contract_address); + println!("result(bytearray): {}", result); + assert(result == "MOCK", 'Symbol (byte array)'); +} + +#[test] +fn test_erc20_felt252() { + // Deploy erc20 + let erc20_class = declare("ERC20Felt252"); + let name: felt252 = 'Mock'; + let symbol: felt252 = 'MOCK'; + let mut calldata: Array = array![]; + name.serialize(ref calldata); + symbol.serialize(ref calldata); + let contract_address = erc20_class.deploy(@calldata).unwrap(); + let result = erc20_versioned_call::get_symbol(contract_address); + println!("result(felt252): {}", result); + assert(result == "MOCK", 'Symbol (felt252)'); +}