Skip to content

Commit

Permalink
feat: Generalize ERC20Token class
Browse files Browse the repository at this point in the history
- ERC20Token was used in instances that were not EVM-specific, which is bad organization.
- We decided that creating a token generic trait was overkill - especially since the main issue was just the Bytes. Instead, we/I decided that a good and simple solution was to:

1. Rename ERC20Token -> Token
2. Make the address Bytes instead of Address
3. Create a helper method to transform the Bytes to Address when needed
  • Loading branch information
TAMARA LIPOWSKI committed Dec 9, 2024
1 parent 21d208e commit b023b7b
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 105 deletions.
13 changes: 5 additions & 8 deletions examples/explorer/data_feed/tycho.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use tycho_simulation::{
},
tycho_models::{AccountUpdate, ResponseAccount},
},
models::ERC20Token,
models::Token,
protocol::{
models::{ProtocolComponent, TryFromWithBlock},
state::ProtocolSim,
Expand Down Expand Up @@ -336,7 +336,7 @@ pub async fn process_messages(
.as_any_mut()
.downcast_mut::<EVMPoolState<PreCachedDB>>()
{
let tokens: Vec<ERC20Token> = vm_state
let tokens: Vec<Token> = vm_state
.tokens
.iter()
.filter_map(|token_address| all_tokens.get(token_address))
Expand All @@ -359,7 +359,7 @@ pub async fn process_messages(
let mut state = stored_state.clone();
if let Some(vm_state) = state.as_any_mut()
.downcast_mut::<EVMPoolState<PreCachedDB>>() {
let tokens: Vec<ERC20Token> = vm_state.tokens
let tokens: Vec<Token> = vm_state.tokens
.iter()
.filter_map(|token_address| all_tokens.get(token_address))
.cloned()
Expand Down Expand Up @@ -427,10 +427,7 @@ pub async fn process_messages(
jh.await.unwrap();
}

pub async fn load_all_tokens(
tycho_url: &str,
auth_key: Option<&str>,
) -> HashMap<Address, ERC20Token> {
pub async fn load_all_tokens(tycho_url: &str, auth_key: Option<&str>) -> HashMap<Address, Token> {
let rpc_url = format!("https://{tycho_url}");
let rpc_client = HttpRPCClient::new(rpc_url.as_str(), auth_key).unwrap();

Expand All @@ -449,7 +446,7 @@ pub async fn load_all_tokens(
}),
)
})
.collect::<HashMap<_, ERC20Token>>()
.collect::<HashMap<_, Token>>()
}

pub fn start(
Expand Down
38 changes: 38 additions & 0 deletions src/evm/protocol/erc20.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use crate::protocol::errors::SimulationError;
use alloy_primitives::{bytes::Bytes, Address};

/// Safely converts a `Bytes` object to an `Address` object.
///
/// Checks the length of the `Bytes` before attempting to convert, and returns a `SimulationError`
/// if not 20 bytes long.
pub fn bytes_to_erc20_address(address: &Bytes) -> Result<Address, SimulationError> {
if address.len() == 20 {
Ok(Address::from_slice(address))
} else {
Err(SimulationError::InvalidInput(
format!("Invalid ERC20 token address: {:?}", address),
None,
))
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::evm::protocol::vm::utils::hexstring_to_vec;
#[test]
fn test_bytes_to_erc20_address_0x() {
let address =
Bytes::from(hexstring_to_vec("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap());
assert_eq!(
bytes_to_erc20_address(&address).unwrap(),
Address::from_slice(&hex::decode("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap())
);
}

#[test]
fn test_bytes_to_erc20_address_invalid() {
let address = Bytes::from(hex::decode("C02aaA").unwrap());
assert!(bytes_to_erc20_address(&address).is_err());
}
}
1 change: 1 addition & 0 deletions src/evm/protocol/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod erc20;
pub mod safe_math;
pub mod u256_num;
pub mod uniswap_v2;
Expand Down
22 changes: 11 additions & 11 deletions src/evm/protocol/uniswap_v2/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
safe_math::{safe_add_u256, safe_div_u256, safe_mul_u256, safe_sub_u256},
u256_num::{biguint_to_u256, u256_to_biguint},
},
models::ERC20Token,
models::Token,
protocol::{
errors::{SimulationError, TransitionError},
models::GetAmountOutResult,
Expand Down Expand Up @@ -59,7 +59,7 @@ impl ProtocolSim for UniswapV2State {
/// # Returns
///
/// * `f64` - Spot price of the tokens.
fn spot_price(&self, base: &ERC20Token, quote: &ERC20Token) -> Result<f64, SimulationError> {
fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError> {
if base < quote {
Ok(spot_price_from_reserves(
self.reserve0,
Expand Down Expand Up @@ -92,8 +92,8 @@ impl ProtocolSim for UniswapV2State {
fn get_amount_out(
&self,
amount_in: BigUint,
token_in: &ERC20Token,
token_out: &ERC20Token,
token_in: &Token,
token_out: &Token,
) -> Result<GetAmountOutResult, SimulationError> {
let amount_in = biguint_to_u256(&amount_in);
if amount_in == U256::from(0u64) {
Expand Down Expand Up @@ -133,7 +133,7 @@ impl ProtocolSim for UniswapV2State {
fn delta_transition(
&mut self,
delta: ProtocolStateDelta,
_tokens: Vec<ERC20Token>,
_tokens: Vec<Token>,
) -> Result<(), TransitionError<String>> {
// reserve0 and reserve1 are considered required attributes and are expected in every delta
// we process
Expand Down Expand Up @@ -213,13 +213,13 @@ mod tests {
#[case] amount_in: BigUint,
#[case] exp: BigUint,
) {
let t0 = ERC20Token::new(
let t0 = Token::new(
"0x0000000000000000000000000000000000000000",
token_0_decimals,
"T0",
10_000.to_biguint().unwrap(),
);
let t1 = ERC20Token::new(
let t1 = Token::new(
"0x0000000000000000000000000000000000000001",
token_1_decimals,
"T0",
Expand Down Expand Up @@ -251,13 +251,13 @@ mod tests {
let amount_in = (BigUint::one() << 256) - BigUint::one(); // U256 max value
let t0d = 18;
let t1d = 16;
let t0 = ERC20Token::new(
let t0 = Token::new(
"0x0000000000000000000000000000000000000000",
t0d,
"T0",
10_000.to_biguint().unwrap(),
);
let t1 = ERC20Token::new(
let t1 = Token::new(
"0x0000000000000000000000000000000000000001",
t1d,
"T0",
Expand All @@ -279,13 +279,13 @@ mod tests {
U256::from_str("36925554990922").unwrap(),
U256::from_str("30314846538607556521556").unwrap(),
);
let usdc = ERC20Token::new(
let usdc = Token::new(
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
6,
"USDC",
10_000.to_biguint().unwrap(),
);
let weth = ERC20Token::new(
let weth = Token::new(
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
18,
"WETH",
Expand Down
4 changes: 2 additions & 2 deletions src/evm/protocol/uniswap_v2/tycho_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::HashMap;
use tycho_client::feed::{synchronizer::ComponentWithState, Header};

use crate::{
models::ERC20Token,
models::Token,
protocol::{errors::InvalidSnapshotError, models::TryFromWithBlock},
};

Expand All @@ -17,7 +17,7 @@ impl TryFromWithBlock<ComponentWithState> for UniswapV2State {
async fn try_from_with_block(
snapshot: ComponentWithState,
_block: Header,
_all_tokens: HashMap<Address, ERC20Token>,
_all_tokens: HashMap<Address, Token>,
) -> Result<Self, Self::Error> {
let reserve0 = U256::from_be_slice(
snapshot
Expand Down
22 changes: 11 additions & 11 deletions src/evm/protocol/uniswap_v3/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
safe_math::{safe_add_u256, safe_sub_u256},
u256_num::u256_to_biguint,
},
models::ERC20Token,
models::Token,
protocol::{
errors::{SimulationError, TransitionError},
models::GetAmountOutResult,
Expand Down Expand Up @@ -240,7 +240,7 @@ impl ProtocolSim for UniswapV3State {
(self.fee as u32) as f64 / 1_000_000.0
}

fn spot_price(&self, a: &ERC20Token, b: &ERC20Token) -> Result<f64, SimulationError> {
fn spot_price(&self, a: &Token, b: &Token) -> Result<f64, SimulationError> {
if a < b {
Ok(sqrt_price_q96_to_f64(self.sqrt_price, a.decimals as u32, b.decimals as u32))
} else {
Expand All @@ -252,8 +252,8 @@ impl ProtocolSim for UniswapV3State {
fn get_amount_out(
&self,
amount_in: BigUint,
token_a: &ERC20Token,
token_b: &ERC20Token,
token_a: &Token,
token_b: &Token,
) -> Result<GetAmountOutResult, SimulationError> {
let zero_for_one = token_a < token_b;
let amount_specified = I256::checked_from_sign_and_abs(
Expand Down Expand Up @@ -285,7 +285,7 @@ impl ProtocolSim for UniswapV3State {
fn delta_transition(
&mut self,
delta: ProtocolStateDelta,
_tokens: Vec<ERC20Token>,
_tokens: Vec<Token>,
) -> Result<(), TransitionError<String>> {
// apply attribute changes
if let Some(liquidity) = delta
Expand Down Expand Up @@ -408,13 +408,13 @@ mod tests {

#[test]
fn test_get_amount_out_full_range_liquidity() {
let token_x = ERC20Token::new(
let token_x = Token::new(
"0x6b175474e89094c44da98b954eedeac495271d0f",
18,
"X",
10_000.to_biguint().unwrap(),
);
let token_y = ERC20Token::new(
let token_y = Token::new(
"0xf1ca9cb74685755965c7458528a36934df52a3ef",
18,
"Y",
Expand Down Expand Up @@ -446,13 +446,13 @@ mod tests {

#[test]
fn test_get_amount_out() {
let wbtc = ERC20Token::new(
let wbtc = Token::new(
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
8,
"WBTC",
10_000.to_biguint().unwrap(),
);
let weth = ERC20Token::new(
let weth = Token::new(
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
18,
"WETH",
Expand Down Expand Up @@ -543,13 +543,13 @@ mod tests {

#[test]
fn test_err_with_partial_trade() {
let dai = ERC20Token::new(
let dai = Token::new(
"0x6b175474e89094c44da98b954eedeac495271d0f",
18,
"DAI",
10_000.to_biguint().unwrap(),
);
let usdc = ERC20Token::new(
let usdc = Token::new(
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
6,
"USDC",
Expand Down
4 changes: 2 additions & 2 deletions src/evm/protocol/uniswap_v3/tycho_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use tycho_client::feed::{synchronizer::ComponentWithState, Header};
use tycho_core::Bytes;

use crate::{
models::ERC20Token,
models::Token,
protocol::{errors::InvalidSnapshotError, models::TryFromWithBlock},
};

Expand All @@ -19,7 +19,7 @@ impl TryFromWithBlock<ComponentWithState> for UniswapV3State {
async fn try_from_with_block(
snapshot: ComponentWithState,
_block: Header,
_all_tokens: HashMap<Address, ERC20Token>,
_all_tokens: HashMap<Address, Token>,
) -> Result<Self, Self::Error> {
let liq = snapshot
.state
Expand Down
Loading

0 comments on commit b023b7b

Please sign in to comment.