1use chrono::Local;
2use ethers_core::{
3 types::{Address, I256, U256},
4 utils::{hex, keccak256},
5};
6use log::info;
7
8pub const EIP1559_FEE_ESTIMATION_THRESHOLD_MAX_CHANGE: i64 = 200;
11pub const OP_MAINNET_CHAIN_ID: usize = 10;
12pub const OP_GOERLI_TESTNET_CHAIN_ID: usize = 420;
13pub const OP_SEPOLIA_TESTNET_CHAIN_ID: usize = 11155420;
14pub const OP_DEVNET_CHAIN_ID: usize = 901;
15pub const BASE_MAINNET_CHAIN_ID: usize = 8453;
16pub const BASE_GOERLI_TESTNET_CHAIN_ID: usize = 84531;
17pub const BASE_SEPOLIA_TESTNET_CHAIN_ID: usize = 84532;
18pub const REDSTONE_HOLESKY_TESTNET_CHAIN_ID: usize = 17001;
19pub const REDSTONE_MAINNET_CHAIN_ID: usize = 690;
20pub const REDSTONE_GARNET_TESTNET_CHAIN_ID: usize = 17069;
21pub const LOOT_MAINNET_CHAIN_ID: usize = 5151706;
22pub const LOOT_TESTNET_CHAIN_ID: usize = 9088912;
23pub const TAIKO_KATLA_TEST_CHAIN_ID: usize = 167008;
24
25pub fn supports_eip1559(chain_id: usize) -> bool {
26 chain_id != LOOT_MAINNET_CHAIN_ID && chain_id != LOOT_TESTNET_CHAIN_ID
27}
28
29pub fn format_now_date() -> String {
30 let fmt = "%Y-%m-%d %H:%M:%S";
31 Local::now().format(fmt).to_string()
32}
33
34pub fn address_to_string(address: Address) -> String {
35 to_checksum(&address, None)
36}
37
38pub fn u256_to_vec(x: &U256) -> Vec<u8> {
39 let mut x_bytes = vec![0u8; 32];
40 x.to_big_endian(&mut x_bytes);
41 x_bytes
42}
43
44pub fn pad_to_bytes32(s: &[u8]) -> Option<[u8; 32]> {
45 let s_len = s.len();
46
47 if s_len > 32 {
48 return None;
49 }
50
51 let mut result: [u8; 32] = Default::default();
52
53 result[..s_len].clone_from_slice(s);
54
55 Some(result)
56}
57
58pub fn ser_bytes_in_hex_string<T, S>(v: &T, s: S) -> Result<S::Ok, S::Error>
59where
60 T: AsRef<[u8]>,
61 S: serde::Serializer,
62{
63 s.serialize_str(&format!("0x{}", hex::encode(v.as_ref())))
65}
66
67pub fn ser_u256_in_dec_string<S>(v: &U256, s: S) -> Result<S::Ok, S::Error>
68where
69 S: serde::Serializer,
70{
71 s.serialize_str(&format!("{}", v))
72}
73
74pub fn to_checksum(addr: &Address, chain_id: Option<u8>) -> String {
77 let prefixed_addr = match chain_id {
78 Some(chain_id) => format!("{}0x{:x}", chain_id, addr),
79 None => format!("{:x}", addr),
80 };
81 let hash = hex::encode(keccak256(prefixed_addr));
82 let hash = hash.as_bytes();
83
84 let addr_hex = hex::encode(addr.as_bytes());
85 let addr_hex = addr_hex.as_bytes();
86
87 addr_hex
88 .iter()
89 .zip(hash)
90 .fold("0x".to_owned(), |mut encoded, (addr, hash)| {
91 encoded.push(if *hash >= 56 {
92 addr.to_ascii_uppercase() as char
93 } else {
94 addr.to_ascii_lowercase() as char
95 });
96 encoded
97 })
98}
99
100pub fn eip1559_gas_price_estimator(base: U256, tips: Vec<Vec<U256>>) -> (U256, U256) {
102 info!("base: {:?}", base);
103 info!("tips: {:?}", tips);
104
105 let max_priority_fee_per_gas = estimate_priority_fee(tips);
106
107 fallback_eip1559_gas_price_estimator(base, max_priority_fee_per_gas)
108}
109
110pub fn fallback_eip1559_gas_price_estimator(
111 base: U256,
112 max_priority_fee_per_gas: U256,
113) -> (U256, U256) {
114 info!("base: {:?}", base);
115 info!("max_priority_fee_per_gas: {:?}", max_priority_fee_per_gas);
116
117 let potential_max_fee = base * 12 / 10;
118
119 let max_fee_per_gas = if max_priority_fee_per_gas > potential_max_fee {
120 max_priority_fee_per_gas + potential_max_fee
121 } else {
122 potential_max_fee
123 };
124 (max_fee_per_gas, max_priority_fee_per_gas)
125}
126
127fn estimate_priority_fee(rewards: Vec<Vec<U256>>) -> U256 {
128 let mut rewards: Vec<U256> = rewards
129 .iter()
130 .map(|r| r[0])
131 .filter(|r| *r > U256::zero())
132 .collect();
133 if rewards.is_empty() {
134 return U256::zero();
135 }
136 if rewards.len() == 1 {
137 return rewards[0];
138 }
139 rewards.sort();
141
142 let mut rewards_copy = rewards.clone();
145 rewards_copy.rotate_left(1);
146
147 let mut percentage_change: Vec<I256> = rewards
148 .iter()
149 .zip(rewards_copy.iter())
150 .map(|(a, b)| {
151 let a = I256::try_from(*a).expect("priority fee overflow");
152 let b = I256::try_from(*b).expect("priority fee overflow");
153 ((b - a) * 100) / a
154 })
155 .collect();
156 percentage_change.pop();
157
158 let max_change = percentage_change.iter().max().unwrap();
160 let max_change_index = percentage_change
161 .iter()
162 .position(|&c| c == *max_change)
163 .unwrap();
164
165 let values = if *max_change >= EIP1559_FEE_ESTIMATION_THRESHOLD_MAX_CHANGE.into()
168 && (max_change_index >= (rewards.len() / 2))
169 {
170 rewards[max_change_index..].to_vec()
171 } else {
172 rewards
173 };
174
175 values[values.len() / 2]
177}
178
179#[cfg(test)]
180pub mod util_tests {
181
182 use ethers_core::types::Address;
183
184 use crate::{address_to_string, format_now_date};
185
186 #[test]
187 fn test_format_now_date() {
188 let res = format_now_date();
189 println!("{:?}", res);
190 }
191
192 #[test]
193 fn test_address_format() {
194 let good_address_in_str = "0x0000000000000000000000000000000000000001";
195 let address = good_address_in_str.parse::<Address>();
196 assert!(address.is_ok());
197 let address_as_str = address_to_string(address.unwrap());
198 assert_eq!(address.unwrap(), address_as_str.parse::<Address>().unwrap());
199
200 let bad_address_in_str = "0x1";
201 let address = bad_address_in_str.parse::<Address>();
202 assert!(address.is_err());
203 }
204}