@@ -254,58 +254,12 @@ fn validate_state(
254254 info ! ( "All expected components were successfully found on Tycho and match the expected state" ) ;
255255
256256 // Step 2: Validate Token Balances
257- // In this step, we validate that the token balances of the components match the values
258- // on-chain, extracted by querying the token balances using a node.
259- let rpc_url = env:: var ( "RPC_URL" )
260- . into_diagnostic ( )
261- . wrap_err ( "Missing ETH_RPC_URL in environment" ) ?;
262- let rpc_provider = RPCProvider :: new ( rpc_url. to_string ( ) ) ;
263-
264- for ( id, component) in components_by_id. iter ( ) {
265- let component_state = protocol_states_by_id. get ( id) ;
266-
267- for token in & component. tokens {
268- let mut balance: U256 = U256 :: from ( 0 ) ;
269-
270- if let Some ( state) = component_state {
271- let bal = state. balances . get ( token) ;
272- if let Some ( bal) = bal {
273- let bal = bal. clone ( ) . into ( ) ;
274- balance = bytes_to_u256 ( bal) ;
275- }
276- }
277-
278- // TODO: Test if balance check works
279- if !skip_balance_check {
280- info ! (
281- "Validating token balance for component {} and token {}" ,
282- component. id, token
283- ) ;
284- let token_address = alloy:: primitives:: Address :: from_slice ( & token[ ..20 ] ) ;
285- let component_address = alloy:: primitives:: Address :: from_str ( component. id . as_str ( ) )
286- . expect ( "Failed to parse component address" ) ;
287- let node_balance = rt. block_on ( rpc_provider. get_token_balance (
288- token_address,
289- component_address,
290- start_block,
291- ) ) ;
292- if balance != node_balance {
293- return Err ( miette ! (
294- "Token balance mismatch for component {} and token {}" ,
295- component. id,
296- token
297- ) )
298- }
299- info ! (
300- "Token balance for component {} and token {} matches the expected value" ,
301- component. id, token
302- ) ;
303- }
304- }
305- }
306257 match skip_balance_check {
307258 true => info ! ( "Skipping balance check" ) ,
308- false => info ! ( "All token balances match the values found onchain" ) ,
259+ false => {
260+ validate_token_balances ( & components_by_id, & protocol_states_by_id, start_block, & rt) ?;
261+ info ! ( "All token balances match the values found onchain" )
262+ }
309263 }
310264
311265 // Step 3: Run Tycho Simulation
@@ -404,9 +358,68 @@ fn validate_state(
404358 Ok ( ( ) )
405359}
406360
361+ /// Validate that the token balances of the components match the values
362+ /// on-chain, extracted by querying the token balances using a node.
363+ fn validate_token_balances (
364+ components_by_id : & HashMap < String , ProtocolComponent > ,
365+ protocol_states_by_id : & HashMap < String , ResponseProtocolState > ,
366+ start_block : u64 ,
367+ rt : & Runtime ,
368+ ) -> miette:: Result < ( ) > {
369+ let rpc_url = env:: var ( "RPC_URL" )
370+ . into_diagnostic ( )
371+ . wrap_err ( "Missing RPC_URL in environment" ) ?;
372+ let rpc_provider = RPCProvider :: new ( rpc_url. to_string ( ) ) ;
373+
374+ for ( id, component) in components_by_id. iter ( ) {
375+ let component_state = protocol_states_by_id. get ( id) ;
376+
377+ for token in & component. tokens {
378+ let mut balance: U256 = U256 :: from ( 0 ) ;
379+
380+ if let Some ( state) = component_state {
381+ let bal = state. balances . get ( token) ;
382+ if let Some ( bal) = bal {
383+ let bal = bal. clone ( ) . into ( ) ;
384+ balance = bytes_to_u256 ( bal) ;
385+ }
386+ }
387+
388+ info ! ( "Validating token balance for component {} and token {}" , component. id, token) ;
389+ let token_address = alloy:: primitives:: Address :: from_slice ( & token[ ..20 ] ) ;
390+ let component_address = alloy:: primitives:: Address :: from_str ( component. id . as_str ( ) )
391+ . expect ( "Failed to parse component address" ) ;
392+ let node_balance = rt. block_on ( rpc_provider. get_token_balance (
393+ token_address,
394+ component_address,
395+ start_block,
396+ ) ) ?;
397+ if balance != node_balance {
398+ return Err ( miette ! (
399+ "Token balance mismatch for component {} and token {}" ,
400+ component. id,
401+ token
402+ ) ) ;
403+ }
404+ info ! (
405+ "Token balance for component {} and token {} matches the expected value" ,
406+ component. id, token
407+ ) ;
408+ }
409+ }
410+ Ok ( ( ) )
411+ }
412+
407413#[ cfg( test) ]
408414mod tests {
415+ use std:: collections:: HashMap ;
416+
417+ use dotenv:: dotenv;
409418 use glob:: glob;
419+ use tycho_common:: {
420+ dto:: { ProtocolComponent , ResponseProtocolState } ,
421+ Bytes ,
422+ } ;
410423
411424 use super :: * ;
412425
@@ -455,4 +468,80 @@ mod tests {
455468 panic ! ( "One or more config files failed to parse." ) ;
456469 }
457470 }
471+
472+ #[ test]
473+ fn test_token_balance_validation ( ) {
474+ // Setup mock data
475+ let block_number = 21998530 ;
476+ let token_bytes = Bytes :: from_str ( "0x0000000000000000000000000000000000000000" ) . unwrap ( ) ;
477+ let component_id = "0x787B8840100d9BaAdD7463f4a73b5BA73B00C6cA" . to_string ( ) ;
478+
479+ let component = ProtocolComponent {
480+ id : component_id. clone ( ) ,
481+ tokens : vec ! [ token_bytes. clone( ) ] ,
482+ ..Default :: default ( )
483+ } ;
484+
485+ let mut balances = HashMap :: new ( ) ;
486+ let balance_bytes = Bytes :: from (
487+ U256 :: from_str ( "1070041574684539264153" )
488+ . unwrap ( )
489+ . to_be_bytes :: < 32 > ( ) ,
490+ ) ;
491+ balances. insert ( token_bytes. clone ( ) , balance_bytes. clone ( ) ) ;
492+ let protocol_state = ResponseProtocolState {
493+ component_id : component_id. clone ( ) ,
494+ balances,
495+ ..Default :: default ( )
496+ } ;
497+
498+ let mut components_by_id = HashMap :: new ( ) ;
499+ components_by_id. insert ( component_id. clone ( ) , component. clone ( ) ) ;
500+ let mut protocol_states_by_id = HashMap :: new ( ) ;
501+ protocol_states_by_id. insert ( component_id. clone ( ) , protocol_state. clone ( ) ) ;
502+
503+ let rt = Runtime :: new ( ) . unwrap ( ) ;
504+ dotenv ( ) . ok ( ) ;
505+ let result =
506+ validate_token_balances ( & components_by_id, & protocol_states_by_id, block_number, & rt) ;
507+ assert ! ( result. is_ok( ) , "Should pass when balance check is performed and balances match" ) ;
508+ }
509+
510+ #[ test]
511+ fn test_token_balance_validation_fails_on_mismatch ( ) {
512+ // Setup mock data
513+ let block_number = 21998530 ;
514+ let token_bytes = Bytes :: from_str ( "0x0000000000000000000000000000000000000000" ) . unwrap ( ) ;
515+ let component_id = "0x787B8840100d9BaAdD7463f4a73b5BA73B00C6cA" . to_string ( ) ;
516+
517+ let component = ProtocolComponent {
518+ id : component_id. clone ( ) ,
519+ tokens : vec ! [ token_bytes. clone( ) ] ,
520+ ..Default :: default ( )
521+ } ;
522+
523+ // Set expected balance to zero
524+ let mut balances = HashMap :: new ( ) ;
525+ let balance_bytes = Bytes :: from ( U256 :: from ( 0 ) . to_be_bytes :: < 32 > ( ) ) ;
526+ balances. insert ( token_bytes. clone ( ) , balance_bytes. clone ( ) ) ;
527+ let protocol_state = ResponseProtocolState {
528+ component_id : component_id. clone ( ) ,
529+ balances,
530+ ..Default :: default ( )
531+ } ;
532+
533+ let mut components_by_id = HashMap :: new ( ) ;
534+ components_by_id. insert ( component_id. clone ( ) , component. clone ( ) ) ;
535+ let mut protocol_states_by_id = HashMap :: new ( ) ;
536+ protocol_states_by_id. insert ( component_id. clone ( ) , protocol_state. clone ( ) ) ;
537+
538+ let rt = Runtime :: new ( ) . unwrap ( ) ;
539+ dotenv ( ) . ok ( ) ;
540+ let result =
541+ validate_token_balances ( & components_by_id, & protocol_states_by_id, block_number, & rt) ;
542+ assert ! (
543+ result. is_err( ) ,
544+ "Should fail when balance check is performed and balances do not match"
545+ ) ;
546+ }
458547}
0 commit comments