1use super::ITIP20;
2use crate::{
3 Precompile, fill_precompile_output, input_cost, metadata, mutate, mutate_void,
4 storage::Handler,
5 tip20::{IRolesAuth, TIP20Token},
6 unknown_selector, view,
7};
8use alloy::{primitives::Address, sol_types::SolCall};
9use revm::precompile::{PrecompileError, PrecompileResult};
10
11impl Precompile for TIP20Token {
12 fn call(&mut self, calldata: &[u8], msg_sender: Address) -> PrecompileResult {
13 self.storage
14 .deduct_gas(input_cost(calldata.len()))
15 .map_err(|_| PrecompileError::OutOfGas)?;
16
17 let selector: [u8; 4] = calldata
18 .get(..4)
19 .ok_or_else(|| {
20 PrecompileError::Other("Invalid input: missing function selector".into())
21 })?
22 .try_into()
23 .unwrap();
24
25 let result = match selector {
26 ITIP20::nameCall::SELECTOR => metadata::<ITIP20::nameCall>(|| self.name()),
28 ITIP20::symbolCall::SELECTOR => metadata::<ITIP20::symbolCall>(|| self.symbol()),
29 ITIP20::decimalsCall::SELECTOR => metadata::<ITIP20::decimalsCall>(|| self.decimals()),
30 ITIP20::currencyCall::SELECTOR => metadata::<ITIP20::currencyCall>(|| self.currency()),
31 ITIP20::totalSupplyCall::SELECTOR => {
32 metadata::<ITIP20::totalSupplyCall>(|| self.total_supply())
33 }
34 ITIP20::supplyCapCall::SELECTOR => {
35 metadata::<ITIP20::supplyCapCall>(|| self.supply_cap())
36 }
37 ITIP20::transferPolicyIdCall::SELECTOR => {
38 metadata::<ITIP20::transferPolicyIdCall>(|| self.transfer_policy_id())
39 }
40 ITIP20::pausedCall::SELECTOR => metadata::<ITIP20::pausedCall>(|| self.paused()),
41
42 ITIP20::balanceOfCall::SELECTOR => {
44 view::<ITIP20::balanceOfCall>(calldata, |call| self.balance_of(call))
45 }
46 ITIP20::allowanceCall::SELECTOR => {
47 view::<ITIP20::allowanceCall>(calldata, |call| self.allowance(call))
48 }
49 ITIP20::quoteTokenCall::SELECTOR => {
50 view::<ITIP20::quoteTokenCall>(calldata, |_| self.quote_token())
51 }
52 ITIP20::nextQuoteTokenCall::SELECTOR => {
53 view::<ITIP20::nextQuoteTokenCall>(calldata, |_| self.next_quote_token())
54 }
55 ITIP20::PAUSE_ROLECall::SELECTOR => {
56 view::<ITIP20::PAUSE_ROLECall>(calldata, |_| Ok(Self::pause_role()))
57 }
58 ITIP20::UNPAUSE_ROLECall::SELECTOR => {
59 view::<ITIP20::UNPAUSE_ROLECall>(calldata, |_| Ok(Self::unpause_role()))
60 }
61 ITIP20::ISSUER_ROLECall::SELECTOR => {
62 view::<ITIP20::ISSUER_ROLECall>(calldata, |_| Ok(Self::issuer_role()))
63 }
64 ITIP20::BURN_BLOCKED_ROLECall::SELECTOR => {
65 view::<ITIP20::BURN_BLOCKED_ROLECall>(calldata, |_| Ok(Self::burn_blocked_role()))
66 }
67
68 ITIP20::transferFromCall::SELECTOR => {
70 mutate::<ITIP20::transferFromCall>(calldata, msg_sender, |s, call| {
71 self.transfer_from(s, call)
72 })
73 }
74 ITIP20::transferCall::SELECTOR => {
75 mutate::<ITIP20::transferCall>(calldata, msg_sender, |s, call| {
76 self.transfer(s, call)
77 })
78 }
79 ITIP20::approveCall::SELECTOR => {
80 mutate::<ITIP20::approveCall>(calldata, msg_sender, |s, call| self.approve(s, call))
81 }
82 ITIP20::changeTransferPolicyIdCall::SELECTOR => {
83 mutate_void::<ITIP20::changeTransferPolicyIdCall>(
84 calldata,
85 msg_sender,
86 |s, call| self.change_transfer_policy_id(s, call),
87 )
88 }
89 ITIP20::setSupplyCapCall::SELECTOR => {
90 mutate_void::<ITIP20::setSupplyCapCall>(calldata, msg_sender, |s, call| {
91 self.set_supply_cap(s, call)
92 })
93 }
94 ITIP20::pauseCall::SELECTOR => {
95 mutate_void::<ITIP20::pauseCall>(calldata, msg_sender, |s, call| {
96 self.pause(s, call)
97 })
98 }
99 ITIP20::unpauseCall::SELECTOR => {
100 mutate_void::<ITIP20::unpauseCall>(calldata, msg_sender, |s, call| {
101 self.unpause(s, call)
102 })
103 }
104 ITIP20::setNextQuoteTokenCall::SELECTOR => {
105 mutate_void::<ITIP20::setNextQuoteTokenCall>(calldata, msg_sender, |s, call| {
106 self.set_next_quote_token(s, call)
107 })
108 }
109 ITIP20::completeQuoteTokenUpdateCall::SELECTOR => {
110 mutate_void::<ITIP20::completeQuoteTokenUpdateCall>(
111 calldata,
112 msg_sender,
113 |s, call| self.complete_quote_token_update(s, call),
114 )
115 }
116
117 ITIP20::feeRecipientCall::SELECTOR => {
118 if !self.storage.spec().is_allegretto() {
119 return unknown_selector(
120 selector,
121 self.storage.gas_used(),
122 self.storage.spec(),
123 );
124 }
125 view::<ITIP20::feeRecipientCall>(calldata, |_call| self.fee_recipient.read())
126 }
127 ITIP20::setFeeRecipientCall::SELECTOR => {
128 if !self.storage.spec().is_allegretto() {
129 return unknown_selector(
130 selector,
131 self.storage.gas_used(),
132 self.storage.spec(),
133 );
134 }
135 mutate_void::<ITIP20::setFeeRecipientCall>(calldata, msg_sender, |s, call| {
136 self.set_fee_recipient(s, call.newRecipient)
137 })
138 }
139
140 ITIP20::mintCall::SELECTOR => {
141 mutate_void::<ITIP20::mintCall>(calldata, msg_sender, |s, call| self.mint(s, call))
142 }
143 ITIP20::mintWithMemoCall::SELECTOR => {
144 mutate_void::<ITIP20::mintWithMemoCall>(calldata, msg_sender, |s, call| {
145 self.mint_with_memo(s, call)
146 })
147 }
148 ITIP20::burnCall::SELECTOR => {
149 mutate_void::<ITIP20::burnCall>(calldata, msg_sender, |s, call| self.burn(s, call))
150 }
151 ITIP20::burnWithMemoCall::SELECTOR => {
152 mutate_void::<ITIP20::burnWithMemoCall>(calldata, msg_sender, |s, call| {
153 self.burn_with_memo(s, call)
154 })
155 }
156 ITIP20::burnBlockedCall::SELECTOR => {
157 mutate_void::<ITIP20::burnBlockedCall>(calldata, msg_sender, |s, call| {
158 self.burn_blocked(s, call)
159 })
160 }
161 ITIP20::transferWithMemoCall::SELECTOR => {
162 mutate_void::<ITIP20::transferWithMemoCall>(calldata, msg_sender, |s, call| {
163 self.transfer_with_memo(s, call)
164 })
165 }
166 ITIP20::transferFromWithMemoCall::SELECTOR => {
167 mutate::<ITIP20::transferFromWithMemoCall>(calldata, msg_sender, |sender, call| {
168 self.transfer_from_with_memo(sender, call)
169 })
170 }
171 ITIP20::startRewardCall::SELECTOR => {
172 mutate::<ITIP20::startRewardCall>(calldata, msg_sender, |s, call| {
173 self.start_reward(s, call)
174 })
175 }
176 ITIP20::setRewardRecipientCall::SELECTOR => {
177 mutate_void::<ITIP20::setRewardRecipientCall>(calldata, msg_sender, |s, call| {
178 self.set_reward_recipient(s, call)
179 })
180 }
181 ITIP20::cancelRewardCall::SELECTOR => {
182 mutate::<ITIP20::cancelRewardCall>(calldata, msg_sender, |s, call| {
183 self.cancel_reward(s, call)
184 })
185 }
186 ITIP20::claimRewardsCall::SELECTOR => {
187 mutate::<ITIP20::claimRewardsCall>(calldata, msg_sender, |_, _| {
188 self.claim_rewards(msg_sender)
189 })
190 }
191
192 ITIP20::finalizeStreamsCall::SELECTOR => {
193 mutate_void::<ITIP20::finalizeStreamsCall>(calldata, msg_sender, |sender, call| {
194 self.finalize_streams(sender, call.timestamp as u128)
195 })
196 }
197
198 ITIP20::totalRewardPerSecondCall::SELECTOR => {
199 view::<ITIP20::totalRewardPerSecondCall>(calldata, |_call| {
200 self.get_total_reward_per_second()
201 })
202 }
203
204 ITIP20::optedInSupplyCall::SELECTOR => {
205 view::<ITIP20::optedInSupplyCall>(calldata, |_call| self.get_opted_in_supply())
206 }
207
208 ITIP20::getStreamCall::SELECTOR => view::<ITIP20::getStreamCall>(calldata, |call| {
209 self.get_stream(call.id).map(|stream| stream.into())
210 }),
211
212 ITIP20::nextStreamIdCall::SELECTOR => {
213 view::<ITIP20::nextStreamIdCall>(calldata, |_call| self.get_next_stream_id())
214 }
215
216 ITIP20::userRewardInfoCall::SELECTOR => {
217 view::<ITIP20::userRewardInfoCall>(calldata, |call| {
218 self.get_user_reward_info(call.account)
219 .map(|info| info.into())
220 })
221 }
222
223 IRolesAuth::hasRoleCall::SELECTOR => {
225 view::<IRolesAuth::hasRoleCall>(calldata, |call| self.has_role(call))
226 }
227 IRolesAuth::getRoleAdminCall::SELECTOR => {
228 view::<IRolesAuth::getRoleAdminCall>(calldata, |call| self.get_role_admin(call))
229 }
230 IRolesAuth::grantRoleCall::SELECTOR => {
231 mutate_void::<IRolesAuth::grantRoleCall>(calldata, msg_sender, |s, call| {
232 self.grant_role(s, call)
233 })
234 }
235 IRolesAuth::revokeRoleCall::SELECTOR => {
236 mutate_void::<IRolesAuth::revokeRoleCall>(calldata, msg_sender, |s, call| {
237 self.revoke_role(s, call)
238 })
239 }
240 IRolesAuth::renounceRoleCall::SELECTOR => {
241 mutate_void::<IRolesAuth::renounceRoleCall>(calldata, msg_sender, |s, call| {
242 self.renounce_role(s, call)
243 })
244 }
245 IRolesAuth::setRoleAdminCall::SELECTOR => {
246 mutate_void::<IRolesAuth::setRoleAdminCall>(calldata, msg_sender, |s, call| {
247 self.set_role_admin(s, call)
248 })
249 }
250
251 _ => unknown_selector(selector, self.storage.gas_used(), self.storage.spec()),
252 };
253
254 result.map(|res| fill_precompile_output(res, &mut self.storage))
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use crate::{
262 PATH_USD_ADDRESS,
263 storage::{StorageCtx, hashmap::HashMapStorageProvider},
264 test_util::setup_storage,
265 tip20::{ISSUER_ROLE, PAUSE_ROLE, TIP20Token, UNPAUSE_ROLE, tests::initialize_path_usd},
266 };
267 use alloy::{
268 primitives::{Bytes, U256},
269 sol_types::{SolInterface, SolValue},
270 };
271 use tempo_chainspec::hardfork::TempoHardfork;
272 use tempo_contracts::precompiles::{RolesAuthError, TIP20Error};
273
274 #[test]
275 fn test_function_selector_dispatch() -> eyre::Result<()> {
276 let (mut storage, sender) = setup_storage();
277 storage.set_spec(TempoHardfork::Moderato);
278 let token_id = 1;
279
280 StorageCtx::enter(&mut storage, || {
281 initialize_path_usd(sender)?;
282 let mut token = TIP20Token::new(token_id);
283 token.initialize(
284 "Test",
285 "TST",
286 "USD",
287 PATH_USD_ADDRESS,
288 sender,
289 Address::ZERO,
290 )?;
291
292 let result = token.call(&Bytes::from([0x12, 0x34, 0x56, 0x78]), sender)?;
294 assert!(result.reverted);
295
296 let result = token.call(&Bytes::from([0x12, 0x34]), sender);
298 assert!(matches!(result, Err(PrecompileError::Other(_))));
299
300 Ok(())
301 })
302 }
303
304 #[test]
305 fn test_balance_of_calldata_handling() -> eyre::Result<()> {
306 let (mut storage, admin) = setup_storage();
307 let sender = Address::random();
308 let account = Address::random();
309 let token_id = 1;
310
311 StorageCtx::enter(&mut storage, || {
312 initialize_path_usd(admin)?;
313
314 let mut token = TIP20Token::new(token_id);
315 token.initialize("Test", "TST", "USD", PATH_USD_ADDRESS, admin, Address::ZERO)?;
316 token.grant_role_internal(admin, *ISSUER_ROLE)?;
317
318 let test_balance = U256::from(1000);
319 token.mint(
320 admin,
321 ITIP20::mintCall {
322 to: account,
323 amount: test_balance,
324 },
325 )?;
326
327 let balance_of_call = ITIP20::balanceOfCall { account };
328 let calldata = balance_of_call.abi_encode();
329
330 let result = token.call(&calldata, sender)?;
331 assert_eq!(result.gas_used, 0);
332
333 let decoded = U256::abi_decode(&result.bytes)?;
334 assert_eq!(decoded, test_balance);
335
336 Ok(())
337 })
338 }
339
340 #[test]
341 fn test_mint_updates_storage() -> eyre::Result<()> {
342 let (mut storage, admin) = setup_storage();
343 let sender = Address::random();
344 let recipient = Address::random();
345 let token_id = 1;
346
347 StorageCtx::enter(&mut storage, || {
348 initialize_path_usd(admin)?;
349
350 let mut token = TIP20Token::new(token_id);
351 token.initialize("Test", "TST", "USD", PATH_USD_ADDRESS, admin, Address::ZERO)?;
352 token.grant_role_internal(sender, *ISSUER_ROLE)?;
353
354 let initial_balance = token.balance_of(ITIP20::balanceOfCall { account: recipient })?;
355 assert_eq!(initial_balance, U256::ZERO);
356
357 let mint_amount = U256::random().min(U256::from(u128::MAX)) % token.supply_cap()?;
358 let mint_call = ITIP20::mintCall {
359 to: recipient,
360 amount: mint_amount,
361 };
362 let calldata = mint_call.abi_encode();
363
364 let result = token.call(&calldata, sender)?;
365 assert_eq!(result.gas_used, 0);
366
367 let final_balance = token.balance_of(ITIP20::balanceOfCall { account: recipient })?;
368 assert_eq!(final_balance, mint_amount);
369
370 Ok(())
371 })
372 }
373
374 #[test]
375 fn test_transfer_updates_balances() -> eyre::Result<()> {
376 let (mut storage, admin) = setup_storage();
377 let sender = Address::random();
378 let recipient = Address::random();
379 let transfer_amount = U256::from(300);
380 let initial_sender_balance = U256::from(1000);
381
382 StorageCtx::enter(&mut storage, || {
383 initialize_path_usd(admin)?;
384 let mut token = TIP20Token::new(1);
385 token.initialize("Test", "TST", "USD", PATH_USD_ADDRESS, admin, Address::ZERO)?;
386 token.grant_role_internal(admin, *ISSUER_ROLE)?;
387 token.mint(
388 admin,
389 ITIP20::mintCall {
390 to: sender,
391 amount: initial_sender_balance,
392 },
393 )?;
394
395 assert_eq!(
396 token.balance_of(ITIP20::balanceOfCall { account: sender })?,
397 initial_sender_balance
398 );
399 assert_eq!(
400 token.balance_of(ITIP20::balanceOfCall { account: recipient })?,
401 U256::ZERO
402 );
403
404 let transfer_call = ITIP20::transferCall {
405 to: recipient,
406 amount: transfer_amount,
407 };
408 let calldata = transfer_call.abi_encode();
409 let result = token.call(&calldata, sender)?;
410 assert_eq!(result.gas_used, 0);
411
412 let success = bool::abi_decode(&result.bytes)?;
413 assert!(success);
414
415 let final_sender_balance =
416 token.balance_of(ITIP20::balanceOfCall { account: sender })?;
417 let final_recipient_balance =
418 token.balance_of(ITIP20::balanceOfCall { account: recipient })?;
419
420 assert_eq!(
421 final_sender_balance,
422 initial_sender_balance - transfer_amount
423 );
424 assert_eq!(final_recipient_balance, transfer_amount);
425
426 Ok(())
427 })
428 }
429
430 #[test]
431 fn test_approve_and_transfer_from() -> eyre::Result<()> {
432 let (mut storage, admin) = setup_storage();
433 let owner = Address::random();
434 let spender = Address::random();
435 let recipient = Address::random();
436 let approve_amount = U256::from(500);
437 let transfer_amount = U256::from(300);
438 let initial_owner_balance = U256::from(1000);
439
440 StorageCtx::enter(&mut storage, || {
441 initialize_path_usd(admin)?;
442 let mut token = TIP20Token::new(1);
443 token.initialize("Test", "TST", "USD", PATH_USD_ADDRESS, admin, Address::ZERO)?;
444 token.grant_role_internal(admin, *ISSUER_ROLE)?;
445 token.mint(
446 admin,
447 ITIP20::mintCall {
448 to: owner,
449 amount: initial_owner_balance,
450 },
451 )?;
452
453 let approve_call = ITIP20::approveCall {
454 spender,
455 amount: approve_amount,
456 };
457 let calldata = approve_call.abi_encode();
458 let result = token.call(&calldata, owner)?;
459 assert_eq!(result.gas_used, 0);
460 let success = bool::abi_decode(&result.bytes)?;
461 assert!(success);
462
463 let allowance = token.allowance(ITIP20::allowanceCall { owner, spender })?;
464 assert_eq!(allowance, approve_amount);
465
466 let transfer_from_call = ITIP20::transferFromCall {
467 from: owner,
468 to: recipient,
469 amount: transfer_amount,
470 };
471 let calldata = transfer_from_call.abi_encode();
472 let result = token.call(&calldata, spender)?;
473 assert_eq!(result.gas_used, 0);
474 let success = bool::abi_decode(&result.bytes)?;
475 assert!(success);
476
477 assert_eq!(
479 token.balance_of(ITIP20::balanceOfCall { account: owner })?,
480 initial_owner_balance - transfer_amount
481 );
482 assert_eq!(
483 token.balance_of(ITIP20::balanceOfCall { account: recipient })?,
484 transfer_amount
485 );
486
487 let remaining_allowance = token.allowance(ITIP20::allowanceCall { owner, spender })?;
489 assert_eq!(remaining_allowance, approve_amount - transfer_amount);
490
491 Ok(())
492 })
493 }
494
495 #[test]
496 fn test_pause_and_unpause() -> eyre::Result<()> {
497 let (mut storage, admin) = setup_storage();
498 let pauser = Address::random();
499 let unpauser = Address::random();
500
501 StorageCtx::enter(&mut storage, || {
502 initialize_path_usd(admin)?;
503 let mut token = TIP20Token::new(1);
504 token.initialize("Test", "TST", "USD", PATH_USD_ADDRESS, admin, Address::ZERO)?;
505 token.grant_role_internal(pauser, *PAUSE_ROLE)?;
506 token.grant_role_internal(unpauser, *UNPAUSE_ROLE)?;
507 assert!(!token.paused()?);
508
509 let pause_call = ITIP20::pauseCall {};
511 let calldata = pause_call.abi_encode();
512 let result = token.call(&calldata, pauser)?;
513 assert_eq!(result.gas_used, 0);
514 assert!(token.paused()?);
515
516 let unpause_call = ITIP20::unpauseCall {};
518 let calldata = unpause_call.abi_encode();
519 let result = token.call(&calldata, unpauser)?;
520 assert_eq!(result.gas_used, 0);
521 assert!(!token.paused()?);
522
523 Ok(())
524 })
525 }
526
527 #[test]
528 fn test_burn_functionality() -> eyre::Result<()> {
529 let (mut storage, admin) = setup_storage();
530 let burner = Address::random();
531 let initial_balance = U256::from(1000);
532 let burn_amount = U256::from(300);
533
534 StorageCtx::enter(&mut storage, || {
535 initialize_path_usd(admin)?;
536 let mut token = TIP20Token::new(1);
537 token.initialize("Test", "TST", "USD", PATH_USD_ADDRESS, admin, Address::ZERO)?;
538 token.grant_role_internal(admin, *ISSUER_ROLE)?;
539 token.grant_role_internal(burner, *ISSUER_ROLE)?;
540
541 token.mint(
543 admin,
544 ITIP20::mintCall {
545 to: burner,
546 amount: initial_balance,
547 },
548 )?;
549
550 assert_eq!(
552 token.balance_of(ITIP20::balanceOfCall { account: burner })?,
553 initial_balance
554 );
555 assert_eq!(token.total_supply()?, initial_balance);
556
557 let burn_call = ITIP20::burnCall {
559 amount: burn_amount,
560 };
561 let calldata = burn_call.abi_encode();
562 let result = token.call(&calldata, burner)?;
563 assert_eq!(result.gas_used, 0);
564 assert_eq!(
565 token.balance_of(ITIP20::balanceOfCall { account: burner })?,
566 initial_balance - burn_amount
567 );
568 assert_eq!(token.total_supply()?, initial_balance - burn_amount);
569
570 Ok(())
571 })
572 }
573
574 #[test]
575 fn test_metadata_functions() -> eyre::Result<()> {
576 let (mut storage, admin) = setup_storage();
577 let caller = Address::random();
578
579 StorageCtx::enter(&mut storage, || {
580 initialize_path_usd(admin)?;
581 let mut token = TIP20Token::new(1);
582 token.initialize(
583 "Test Token",
584 "TEST",
585 "USD",
586 PATH_USD_ADDRESS,
587 admin,
588 Address::ZERO,
589 )?;
590
591 let name_call = ITIP20::nameCall {};
593 let calldata = name_call.abi_encode();
594 let result = token.call(&calldata, caller)?;
595 assert_eq!(result.gas_used, 0);
597 let name = String::abi_decode(&result.bytes)?;
598 assert_eq!(name, "Test Token");
599
600 let symbol_call = ITIP20::symbolCall {};
602 let calldata = symbol_call.abi_encode();
603 let result = token.call(&calldata, caller)?;
604 assert_eq!(result.gas_used, 0);
605 let symbol = String::abi_decode(&result.bytes)?;
606 assert_eq!(symbol, "TEST");
607
608 let decimals_call = ITIP20::decimalsCall {};
610 let calldata = decimals_call.abi_encode();
611 let result = token.call(&calldata, caller)?;
612 assert_eq!(result.gas_used, 0);
613 let decimals = ITIP20::decimalsCall::abi_decode_returns(&result.bytes)?;
614 assert_eq!(decimals, 6);
615
616 let currency_call = ITIP20::currencyCall {};
618 let calldata = currency_call.abi_encode();
619 let result = token.call(&calldata, caller)?;
620 assert_eq!(result.gas_used, 0);
621 let currency = String::abi_decode(&result.bytes)?;
622 assert_eq!(currency, "USD");
623
624 let total_supply_call = ITIP20::totalSupplyCall {};
626 let calldata = total_supply_call.abi_encode();
627 let result = token.call(&calldata, caller)?;
628 assert_eq!(result.gas_used, 0);
630 let total_supply = U256::abi_decode(&result.bytes)?;
631 assert_eq!(total_supply, U256::ZERO);
632
633 Ok(())
634 })
635 }
636
637 #[test]
638 fn test_supply_cap_enforcement() -> eyre::Result<()> {
639 let (mut storage, admin) = setup_storage();
640 let recipient = Address::random();
641 let supply_cap = U256::from(1000);
642 let mint_amount = U256::from(1001);
643
644 StorageCtx::enter(&mut storage, || {
645 initialize_path_usd(admin)?;
646 let mut token = TIP20Token::new(1);
647 token.initialize("Test", "TST", "USD", PATH_USD_ADDRESS, admin, Address::ZERO)?;
648 token.grant_role_internal(admin, *ISSUER_ROLE)?;
649
650 let set_cap_call = ITIP20::setSupplyCapCall {
651 newSupplyCap: supply_cap,
652 };
653 let calldata = set_cap_call.abi_encode();
654 let result = token.call(&calldata, admin)?;
655 assert_eq!(result.gas_used, 0);
656
657 let mint_call = ITIP20::mintCall {
658 to: recipient,
659 amount: mint_amount,
660 };
661 let calldata = mint_call.abi_encode();
662 let output = token.call(&calldata, admin)?;
663 assert!(output.reverted);
664
665 let expected: Bytes = TIP20Error::supply_cap_exceeded().selector().into();
666 assert_eq!(output.bytes, expected);
667
668 Ok(())
669 })
670 }
671
672 #[test]
673 fn test_role_based_access_control() -> eyre::Result<()> {
674 let (mut storage, admin) = setup_storage();
675 let user1 = Address::random();
676 let user2 = Address::random();
677 let unauthorized = Address::random();
678
679 StorageCtx::enter(&mut storage, || {
680 initialize_path_usd(admin)?;
681 let mut token = TIP20Token::new(1);
682 token.initialize("Test", "TST", "USD", PATH_USD_ADDRESS, admin, Address::ZERO)?;
683
684 token.grant_role_internal(user1, *ISSUER_ROLE)?;
685 let has_role_call = IRolesAuth::hasRoleCall {
686 role: *ISSUER_ROLE,
687 account: user1,
688 };
689 let calldata = has_role_call.abi_encode();
690 let result = token.call(&calldata, admin)?;
691 assert_eq!(result.gas_used, 0);
692 let has_role = bool::abi_decode(&result.bytes)?;
693 assert!(has_role);
694
695 let has_role_call = IRolesAuth::hasRoleCall {
696 role: *ISSUER_ROLE,
697 account: user2,
698 };
699 let calldata = has_role_call.abi_encode();
700 let result = token.call(&calldata, admin)?;
701 let has_role = bool::abi_decode(&result.bytes)?;
702 assert!(!has_role);
703
704 let mint_call = ITIP20::mintCall {
705 to: user2,
706 amount: U256::from(100),
707 };
708 let calldata = mint_call.abi_encode();
709 let output = token.call(&Bytes::from(calldata.clone()), unauthorized)?;
710 assert!(output.reverted);
711 let expected: Bytes = RolesAuthError::unauthorized().selector().into();
712 assert_eq!(output.bytes, expected);
713
714 let result = token.call(&calldata, user1)?;
715 assert_eq!(result.gas_used, 0);
716
717 Ok(())
718 })
719 }
720
721 #[test]
722 fn test_transfer_with_memo() -> eyre::Result<()> {
723 let (mut storage, admin) = setup_storage();
724 let sender = Address::random();
725 let recipient = Address::random();
726 let transfer_amount = U256::from(100);
727 let initial_balance = U256::from(500);
728
729 StorageCtx::enter(&mut storage, || {
730 initialize_path_usd(admin)?;
731 let mut token = TIP20Token::new(1);
732 token.initialize("Test", "TST", "USD", PATH_USD_ADDRESS, admin, Address::ZERO)?;
733 token.grant_role_internal(admin, *ISSUER_ROLE)?;
734 token.mint(
735 admin,
736 ITIP20::mintCall {
737 to: sender,
738 amount: initial_balance,
739 },
740 )?;
741
742 let memo = alloy::primitives::B256::from([1u8; 32]);
743 let transfer_call = ITIP20::transferWithMemoCall {
744 to: recipient,
745 amount: transfer_amount,
746 memo,
747 };
748 let calldata = transfer_call.abi_encode();
749 let result = token.call(&calldata, sender)?;
750 assert_eq!(result.gas_used, 0);
751 assert_eq!(
752 token.balance_of(ITIP20::balanceOfCall { account: sender })?,
753 initial_balance - transfer_amount
754 );
755 assert_eq!(
756 token.balance_of(ITIP20::balanceOfCall { account: recipient })?,
757 transfer_amount
758 );
759
760 Ok(())
761 })
762 }
763
764 #[test]
765 fn test_change_transfer_policy_id() -> eyre::Result<()> {
766 let (mut storage, admin) = setup_storage();
767 let non_admin = Address::random();
768 let new_policy_id = 42u64;
769
770 StorageCtx::enter(&mut storage, || {
771 initialize_path_usd(admin)?;
772 let mut token = TIP20Token::new(1);
773 token.initialize("Test", "TST", "USD", PATH_USD_ADDRESS, admin, Address::ZERO)?;
774
775 let change_policy_call = ITIP20::changeTransferPolicyIdCall {
776 newPolicyId: new_policy_id,
777 };
778 let calldata = change_policy_call.abi_encode();
779 let result = token.call(&calldata, admin)?;
780 assert_eq!(result.gas_used, 0);
781 assert_eq!(token.transfer_policy_id()?, new_policy_id);
782
783 let change_policy_call = ITIP20::changeTransferPolicyIdCall { newPolicyId: 100 };
784 let calldata = change_policy_call.abi_encode();
785 let output = token.call(&calldata, non_admin)?;
786 assert!(output.reverted);
787 let expected: Bytes = RolesAuthError::unauthorized().selector().into();
788 assert_eq!(output.bytes, expected);
789
790 Ok(())
791 })
792 }
793
794 #[test]
795 fn tip20_test_selector_coverage() {
796 use crate::test_util::{assert_full_coverage, check_selector_coverage};
797 use tempo_contracts::precompiles::{IRolesAuth::IRolesAuthCalls, ITIP20::ITIP20Calls};
798
799 let (mut storage, admin) = setup_storage();
800
801 StorageCtx::enter(&mut storage, || {
802 initialize_path_usd(admin).unwrap();
803 let mut token = TIP20Token::new(1);
804 token
805 .initialize("Test", "TST", "USD", PATH_USD_ADDRESS, admin, Address::ZERO)
806 .unwrap();
807
808 let itip20_unsupported =
809 check_selector_coverage(&mut token, ITIP20Calls::SELECTORS, "ITIP20", |s| {
810 ITIP20Calls::name_by_selector(s)
811 });
812
813 let roles_unsupported = check_selector_coverage(
814 &mut token,
815 IRolesAuthCalls::SELECTORS,
816 "IRolesAuth",
817 IRolesAuthCalls::name_by_selector,
818 );
819
820 assert_full_coverage([itip20_unsupported, roles_unsupported]);
821 })
822 }
823
824 #[test]
825 fn test_fee_recipient_pre_allegretto() -> eyre::Result<()> {
826 let mut storage = HashMapStorageProvider::new(1).with_spec(TempoHardfork::Adagio);
827 let admin = Address::random();
828
829 StorageCtx::enter(&mut storage, || {
830 initialize_path_usd(admin)?;
831 let mut token = TIP20Token::new(1);
832 token.initialize(
833 "Test",
834 "TST",
835 "USD",
836 PATH_USD_ADDRESS,
837 admin,
838 Address::from([0x11; 20]),
839 )?;
840
841 let call = ITIP20::feeRecipientCall {};
842 let calldata = call.abi_encode();
843 let result = token.call(&Bytes::from(calldata), admin);
844
845 assert!(matches!(
846 result,
847 Err(revm::precompile::PrecompileError::Other(ref msg)) if msg.contains("Unknown function selector")
848 ));
849
850 Ok(())
851 })
852 }
853
854 #[test]
855 fn test_fee_recipient_post_allegretto() -> eyre::Result<()> {
856 let mut storage = HashMapStorageProvider::new(1).with_spec(TempoHardfork::Allegretto);
857 let admin = Address::random();
858 let fee_recipient = Address::random();
859
860 StorageCtx::enter(&mut storage, || {
861 initialize_path_usd(admin)?;
862 let mut token = TIP20Token::new(1);
863 token.initialize("Test", "TST", "USD", PATH_USD_ADDRESS, admin, fee_recipient)?;
864
865 let call = ITIP20::feeRecipientCall {};
866 let calldata = call.abi_encode();
867 let result = token.call(&Bytes::from(calldata), admin)?;
868
869 assert!(!result.reverted);
870 let recipient = ITIP20::feeRecipientCall::abi_decode_returns(&result.bytes)?;
871 assert_eq!(recipient, fee_recipient);
872 Ok(())
873 })
874 }
875
876 #[test]
877 fn test_set_fee_recipient_pre_allegretto() -> eyre::Result<()> {
878 let mut storage = HashMapStorageProvider::new(1).with_spec(TempoHardfork::Adagio);
879 let admin = Address::random();
880
881 StorageCtx::enter(&mut storage, || {
882 initialize_path_usd(admin)?;
883 let mut token = TIP20Token::new(1);
884 token.initialize("Test", "TST", "USD", PATH_USD_ADDRESS, admin, admin)?;
885
886 let call = ITIP20::setFeeRecipientCall {
887 newRecipient: Address::from([0x33; 20]),
888 };
889 let calldata = call.abi_encode();
890 let result = token.call(&Bytes::from(calldata), admin);
891
892 assert!(matches!(
893 result,
894 Err(revm::precompile::PrecompileError::Other(ref msg)) if msg.contains("Unknown function selector")
895 ));
896
897 Ok(())
898 })
899 }
900
901 #[test]
902 fn test_set_fee_recipient_post_allegretto() -> eyre::Result<()> {
903 let mut storage = HashMapStorageProvider::new(1).with_spec(TempoHardfork::Allegretto);
904 let admin = Address::random();
905 let new_recipient = Address::random();
906
907 StorageCtx::enter(&mut storage, || {
908 initialize_path_usd(admin)?;
909 let mut token = TIP20Token::new(1);
910 token.initialize("Test", "TST", "USD", PATH_USD_ADDRESS, admin, admin)?;
911
912 let call = ITIP20::setFeeRecipientCall {
913 newRecipient: new_recipient,
914 };
915 let calldata = call.abi_encode();
916 let result = token.call(&Bytes::from(calldata), admin)?;
917
918 assert!(!result.reverted);
919
920 let call = ITIP20::feeRecipientCall {};
921 let calldata = call.abi_encode();
922 let result = token.call(&Bytes::from(calldata), admin)?;
923
924 let recipient = ITIP20::feeRecipientCall::abi_decode_returns(&result.bytes)?;
925 assert_eq!(recipient, new_recipient);
926
927 Ok(())
928 })
929 }
930}