tempo_precompiles/tip_fee_manager/
dispatch.rs1use crate::{
4 Precompile, dispatch_call, input_cost, metadata, mutate, mutate_void,
5 storage::Handler,
6 tip_fee_manager::{
7 ITIPFeeAMM, TipFeeManager,
8 amm::{M, MIN_LIQUIDITY, N, SCALE},
9 },
10 view,
11};
12use alloy::{primitives::Address, sol_types::SolInterface};
13use revm::precompile::{PrecompileError, PrecompileResult};
14use tempo_contracts::precompiles::{IFeeManager::IFeeManagerCalls, ITIPFeeAMM::ITIPFeeAMMCalls};
15
16enum TipFeeManagerCall {
18 FeeManager(IFeeManagerCalls),
19 Amm(ITIPFeeAMMCalls),
20}
21
22impl TipFeeManagerCall {
23 fn decode(calldata: &[u8]) -> Result<Self, alloy::sol_types::Error> {
24 let selector: [u8; 4] = calldata[..4].try_into().expect("calldata len >= 4");
26
27 if IFeeManagerCalls::valid_selector(selector) {
28 IFeeManagerCalls::abi_decode(calldata).map(Self::FeeManager)
29 } else {
30 ITIPFeeAMMCalls::abi_decode(calldata).map(Self::Amm)
31 }
32 }
33}
34
35impl Precompile for TipFeeManager {
36 fn call(&mut self, calldata: &[u8], msg_sender: Address) -> PrecompileResult {
37 self.storage
38 .deduct_gas(input_cost(calldata.len()))
39 .map_err(|_| PrecompileError::OutOfGas)?;
40
41 dispatch_call(calldata, TipFeeManagerCall::decode, |call| match call {
42 TipFeeManagerCall::FeeManager(IFeeManagerCalls::userTokens(call)) => {
44 view(call, |c| self.user_tokens(c))
45 }
46 TipFeeManagerCall::FeeManager(IFeeManagerCalls::validatorTokens(call)) => {
47 view(call, |c| self.validator_tokens(c))
48 }
49 TipFeeManagerCall::FeeManager(IFeeManagerCalls::collectedFees(call)) => {
50 view(call, |c| self.collected_fees[c.validator][c.token].read())
51 }
52
53 TipFeeManagerCall::FeeManager(IFeeManagerCalls::setValidatorToken(call)) => {
55 mutate_void(call, msg_sender, |s, c| {
56 let beneficiary = self.storage.beneficiary();
57 self.set_validator_token(s, c, beneficiary)
58 })
59 }
60 TipFeeManagerCall::FeeManager(IFeeManagerCalls::setUserToken(call)) => {
61 mutate_void(call, msg_sender, |s, c| self.set_user_token(s, c))
62 }
63 TipFeeManagerCall::FeeManager(IFeeManagerCalls::distributeFees(call)) => {
64 mutate_void(call, msg_sender, |_, c| {
65 self.distribute_fees(c.validator, c.token)
66 })
67 }
68
69 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::M(_)) => {
71 metadata::<ITIPFeeAMM::MCall>(|| Ok(M))
72 }
73 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::N(_)) => {
74 metadata::<ITIPFeeAMM::NCall>(|| Ok(N))
75 }
76 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::SCALE(_)) => {
77 metadata::<ITIPFeeAMM::SCALECall>(|| Ok(SCALE))
78 }
79 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::MIN_LIQUIDITY(_)) => {
80 metadata::<ITIPFeeAMM::MIN_LIQUIDITYCall>(|| Ok(MIN_LIQUIDITY))
81 }
82
83 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::getPoolId(call)) => {
85 view(call, |c| Ok(self.pool_id(c.userToken, c.validatorToken)))
86 }
87 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::getPool(call)) => view(call, |c| {
88 let pool = self.get_pool(c)?;
89 Ok(ITIPFeeAMM::Pool {
90 reserveUserToken: pool.reserve_user_token,
91 reserveValidatorToken: pool.reserve_validator_token,
92 })
93 }),
94 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::pools(call)) => view(call, |c| {
95 let pool = self.pools[c.poolId].read()?;
96 Ok(ITIPFeeAMM::Pool {
97 reserveUserToken: pool.reserve_user_token,
98 reserveValidatorToken: pool.reserve_validator_token,
99 })
100 }),
101 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::totalSupply(call)) => {
102 view(call, |c| self.total_supply[c.poolId].read())
103 }
104 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::liquidityBalances(call)) => {
105 view(call, |c| self.liquidity_balances[c.poolId][c.user].read())
106 }
107
108 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::mint(call)) => {
110 mutate(call, msg_sender, |s, c| {
111 self.mint(
112 s,
113 c.userToken,
114 c.validatorToken,
115 c.amountValidatorToken,
116 c.to,
117 )
118 })
119 }
120 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::burn(call)) => {
121 mutate(call, msg_sender, |s, c| {
122 let (amount_user_token, amount_validator_token) =
123 self.burn(s, c.userToken, c.validatorToken, c.liquidity, c.to)?;
124 Ok(ITIPFeeAMM::burnReturn {
125 amountUserToken: amount_user_token,
126 amountValidatorToken: amount_validator_token,
127 })
128 })
129 }
130 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::rebalanceSwap(call)) => {
131 mutate(call, msg_sender, |s, c| {
132 self.rebalance_swap(s, c.userToken, c.validatorToken, c.amountOut, c.to)
133 })
134 }
135 })
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use crate::{
143 Precompile, expect_precompile_revert,
144 storage::{ContractStorage, StorageCtx, hashmap::HashMapStorageProvider},
145 test_util::{TIP20Setup, assert_full_coverage, check_selector_coverage},
146 tip_fee_manager::{
147 FeeManagerError,
148 amm::{M, MIN_LIQUIDITY, N, PoolKey, SCALE},
149 },
150 };
151 use alloy::{
152 primitives::{Address, B256, U256},
153 sol_types::{SolCall, SolValue},
154 };
155 use tempo_contracts::precompiles::{
156 IFeeManager, IFeeManager::IFeeManagerCalls, ITIPFeeAMM, ITIPFeeAMM::ITIPFeeAMMCalls,
157 };
158
159 #[test]
160 fn test_set_validator_token() -> eyre::Result<()> {
161 let mut storage = HashMapStorageProvider::new(1);
162 let admin = Address::random();
163 let validator = Address::random();
164 StorageCtx::enter(&mut storage, || {
165 let token = TIP20Setup::create("TestToken", "TST", admin).apply()?;
166 let mut fee_manager = TipFeeManager::new();
167
168 let calldata = IFeeManager::setValidatorTokenCall {
169 token: token.address(),
170 }
171 .abi_encode();
172 let result = fee_manager.call(&calldata, validator)?;
173 assert_eq!(result.gas_used, 0);
174
175 let calldata = IFeeManager::validatorTokensCall { validator }.abi_encode();
177 let result = fee_manager.call(&calldata, validator)?;
178 assert_eq!(result.gas_used, 0);
179 let returned_token = Address::abi_decode(&result.bytes)?;
180 assert_eq!(returned_token, token.address());
181
182 Ok(())
183 })
184 }
185
186 #[test]
187 fn test_set_validator_token_zero_address() -> eyre::Result<()> {
188 let mut storage = HashMapStorageProvider::new(1);
189 let validator = Address::random();
190 StorageCtx::enter(&mut storage, || {
191 let mut fee_manager = TipFeeManager::new();
192
193 let calldata = IFeeManager::setValidatorTokenCall {
194 token: Address::ZERO,
195 }
196 .abi_encode();
197 let result = fee_manager.call(&calldata, validator);
198 expect_precompile_revert(&result, FeeManagerError::invalid_token());
199
200 Ok(())
201 })
202 }
203
204 #[test]
205 fn test_set_user_token() -> eyre::Result<()> {
206 let mut storage = HashMapStorageProvider::new(1);
207 let admin = Address::random();
208 let user = Address::random();
209 StorageCtx::enter(&mut storage, || {
210 let token = TIP20Setup::create("TestToken", "TST", admin).apply()?;
211 let mut fee_manager = TipFeeManager::new();
212
213 let calldata = IFeeManager::setUserTokenCall {
214 token: token.address(),
215 }
216 .abi_encode();
217 let result = fee_manager.call(&calldata, user)?;
218 assert_eq!(result.gas_used, 0);
219
220 let calldata = IFeeManager::userTokensCall { user }.abi_encode();
222 let result = fee_manager.call(&calldata, user)?;
223 assert_eq!(result.gas_used, 0);
224 let returned_token = Address::abi_decode(&result.bytes)?;
225 assert_eq!(returned_token, token.address());
226
227 Ok(())
228 })
229 }
230
231 #[test]
232 fn test_set_user_token_zero_address() -> eyre::Result<()> {
233 let mut storage = HashMapStorageProvider::new(1);
234 let user = Address::random();
235 StorageCtx::enter(&mut storage, || {
236 let mut fee_manager = TipFeeManager::new();
237
238 let calldata = IFeeManager::setUserTokenCall {
239 token: Address::ZERO,
240 }
241 .abi_encode();
242 let result = fee_manager.call(&calldata, user);
243 expect_precompile_revert(&result, FeeManagerError::invalid_token());
244
245 Ok(())
246 })
247 }
248
249 #[test]
250 fn test_get_pool_id() -> eyre::Result<()> {
251 let mut storage = HashMapStorageProvider::new(1);
252 let token_a = Address::random();
253 let token_b = Address::random();
254 let sender = Address::random();
255 StorageCtx::enter(&mut storage, || {
256 let mut fee_manager = TipFeeManager::new();
257
258 let calldata = ITIPFeeAMM::getPoolIdCall {
259 userToken: token_a,
260 validatorToken: token_b,
261 }
262 .abi_encode();
263 let result = fee_manager.call(&calldata, sender)?;
264 assert_eq!(result.gas_used, 0);
265
266 let returned_id = B256::abi_decode(&result.bytes)?;
267 let expected_id = PoolKey::new(token_a, token_b).get_id();
268 assert_eq!(returned_id, expected_id);
269
270 Ok(())
271 })
272 }
273
274 #[test]
275 fn test_tip_fee_amm_pool_operations() -> eyre::Result<()> {
276 let mut storage = HashMapStorageProvider::new(1);
277 let token_a = Address::random();
278 let token_b = Address::random();
279 let sender = Address::random();
280 StorageCtx::enter(&mut storage, || {
281 let mut fee_manager = TipFeeManager::new();
282
283 let get_pool_call = ITIPFeeAMM::getPoolCall {
285 userToken: token_a,
286 validatorToken: token_b,
287 };
288 let calldata = get_pool_call.abi_encode();
289 let result = fee_manager.call(&calldata, sender)?;
290 assert_eq!(result.gas_used, 0);
291
292 let pool = ITIPFeeAMM::Pool::abi_decode(&result.bytes)?;
294 assert_eq!(pool.reserveUserToken, 0);
295 assert_eq!(pool.reserveValidatorToken, 0);
296
297 Ok(())
298 })
299 }
300
301 #[test]
302 fn test_pool_id_calculation() -> eyre::Result<()> {
303 let mut storage = HashMapStorageProvider::new(1);
304 let token_a = Address::random();
305 let token_b = Address::random();
306 let sender = Address::random();
307 StorageCtx::enter(&mut storage, || {
308 let mut fee_manager = TipFeeManager::new();
309
310 let calldata1 = ITIPFeeAMM::getPoolIdCall {
312 userToken: token_a,
313 validatorToken: token_b,
314 }
315 .abi_encode();
316 let result1 = fee_manager.call(&calldata1, sender)?;
317 let id1 = B256::abi_decode(&result1.bytes)?;
318
319 let calldata2 = ITIPFeeAMM::getPoolIdCall {
321 userToken: token_b,
322 validatorToken: token_a,
323 }
324 .abi_encode();
325 let result2 = fee_manager.call(&calldata2, sender)?;
326 let id2 = B256::abi_decode(&result2.bytes)?;
327
328 assert_ne!(id1, id2);
330
331 Ok(())
332 })
333 }
334
335 #[test]
336 fn test_fee_manager_invalid_token_error() -> eyre::Result<()> {
337 let mut storage = HashMapStorageProvider::new(1);
338 let user = Address::random();
339 let validator = Address::random();
340 StorageCtx::enter(&mut storage, || {
341 let mut fee_manager = TipFeeManager::new();
342
343 let set_validator_call = IFeeManager::setValidatorTokenCall {
345 token: Address::ZERO,
346 };
347 let result = fee_manager.call(&set_validator_call.abi_encode(), validator);
348 expect_precompile_revert(&result, FeeManagerError::invalid_token());
349
350 let set_user_call = IFeeManager::setUserTokenCall {
352 token: Address::ZERO,
353 };
354 let result = fee_manager.call(&set_user_call.abi_encode(), user);
355 expect_precompile_revert(&result, FeeManagerError::invalid_token());
356
357 Ok(())
358 })
359 }
360
361 #[test]
362 fn test_amm_constants() -> eyre::Result<()> {
363 let mut storage = HashMapStorageProvider::new(1);
364 let sender = Address::random();
365 StorageCtx::enter(&mut storage, || {
366 let mut fee_manager = TipFeeManager::new();
367
368 let result =
369 fee_manager.call(&ITIPFeeAMM::MIN_LIQUIDITYCall {}.abi_encode(), sender)?;
370 assert!(!result.reverted);
371 assert_eq!(U256::abi_decode(&result.bytes)?, MIN_LIQUIDITY);
372
373 let result = fee_manager.call(&ITIPFeeAMM::MCall {}.abi_encode(), sender)?;
374 assert_eq!(U256::abi_decode(&result.bytes)?, M);
375
376 let result = fee_manager.call(&ITIPFeeAMM::NCall {}.abi_encode(), sender)?;
377 assert_eq!(U256::abi_decode(&result.bytes)?, N);
378
379 let result = fee_manager.call(&ITIPFeeAMM::SCALECall {}.abi_encode(), sender)?;
380 assert_eq!(U256::abi_decode(&result.bytes)?, SCALE);
381
382 Ok(())
383 })
384 }
385
386 #[test]
387 fn test_tip_fee_manager_selector_coverage() -> eyre::Result<()> {
388 let mut storage = HashMapStorageProvider::new(1);
389 StorageCtx::enter(&mut storage, || {
390 let mut fee_manager = TipFeeManager::new();
391
392 let fee_manager_unsupported = check_selector_coverage(
393 &mut fee_manager,
394 IFeeManagerCalls::SELECTORS,
395 "IFeeManager",
396 IFeeManagerCalls::name_by_selector,
397 );
398
399 let amm_unsupported = check_selector_coverage(
400 &mut fee_manager,
401 ITIPFeeAMMCalls::SELECTORS,
402 "ITIPFeeAMM",
403 ITIPFeeAMMCalls::name_by_selector,
404 );
405
406 assert_full_coverage([fee_manager_unsupported, amm_unsupported]);
407
408 Ok(())
409 })
410 }
411}