Skip to content

Commit

Permalink
Merge pull request #1 from haiko-xyz/parketh/feat-handle-felt252-or-b…
Browse files Browse the repository at this point in the history
…ytearray-erc20

fix: add versioned erc20 call for `felt252` symbols
  • Loading branch information
parketh committed May 6, 2024
2 parents 6553c38 + 528615f commit 95da3a7
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 5 deletions.
Empty file.
2 changes: 2 additions & 0 deletions src/contracts/mocks.cairo
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
pub(crate) mod store_packing_contract;
pub(crate) mod erc20_felt252;
pub(crate) mod erc20_bytearray;
34 changes: 34 additions & 0 deletions src/contracts/mocks/erc20_bytearray.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#[starknet::interface]
pub trait IERC20ByteArray<TContractState> {
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<ContractState> {
fn name(self: @ContractState) -> ByteArray {
self.name.read()
}

fn symbol(self: @ContractState) -> ByteArray {
self.symbol.read()
}
}
}
34 changes: 34 additions & 0 deletions src/contracts/mocks/erc20_felt252.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#[starknet::interface]
pub trait IERC20Felt252<TContractState> {
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<ContractState> {
fn name(self: @ContractState) -> felt252 {
self.name.read()
}

fn symbol(self: @ContractState) -> felt252 {
self.symbol.read()
}
}
}
13 changes: 8 additions & 5 deletions src/contracts/strategy.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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}};
Expand Down Expand Up @@ -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
);
Expand Down
1 change: 1 addition & 0 deletions src/interfaces.cairo
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod IReversionStrategy;
pub mod IVaultToken;
pub mod IERC20Metadata;
11 changes: 11 additions & 0 deletions src/interfaces/IERC20Metadata.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#[starknet::interface]
pub trait IERC20MetadataFelt252<TContractState> {
fn name(self: @TContractState) -> felt252;
fn symbol(self: @TContractState) -> felt252;
}

#[starknet::interface]
pub trait IERC20MetadataByteArray<TContractState> {
fn name(self: @TContractState) -> ByteArray;
fn symbol(self: @TContractState) -> ByteArray;
}
1 change: 1 addition & 0 deletions src/libraries.cairo
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod trend_math;
pub mod erc20_versioned_call;
pub(crate) mod store_packing;
60 changes: 60 additions & 0 deletions src/libraries/erc20_versioned_call.cairo
Original file line number Diff line number Diff line change
@@ -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::<felt252>::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
}
1 change: 1 addition & 0 deletions src/tests.cairo
Original file line number Diff line number Diff line change
@@ -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;
44 changes: 44 additions & 0 deletions src/tests/test_erc20_interface.cairo
Original file line number Diff line number Diff line change
@@ -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<felt252> = 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)');
}

0 comments on commit 95da3a7

Please sign in to comment.