@@ -11,9 +11,26 @@ use crate::{
1111
1212pub const MIN_TOKEN_POOL_ACCOUNTS : usize = 13 ; // see TokenAccounts struct for all required accounts
1313const U160_MAX : U256 = U256 :: from_words ( u32:: MAX as u128 , u128:: MAX ) ;
14- const EVM_PRECOMPILE_SPACE : u32 = 1024 ;
1514pub const V1_TOKEN_ADMIN_REGISTRY_SIZE : usize = 169 ; // for migration v1->v2 of the TokenAdminRegistry, which adds the `supports_auto_derivation` field.
1615
16+ /// 32-byte big-endian uint256 representation of 11 (0x0b)
17+ pub const APTOS_PRECOMPILE_SPACE : [ u8 ; 32 ] = [
18+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
19+ 0x0b ,
20+ ] ;
21+
22+ /// 32-byte big-endian uint256 representation of 1024 (0x0400)
23+ pub const EVM_PRECOMPILE_SPACE : [ u8 ; 32 ] = [
24+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0x04 ,
25+ 0x00 ,
26+ ] ;
27+
28+ /// 32-byte big-endian uint256 representation of 0xdee9
29+ pub const SUI_PRECOMPILE_SPACE : [ u8 ; 32 ] = [
30+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0xde ,
31+ 0xe9 ,
32+ ] ;
33+
1734pub struct TokenAccounts < ' a > {
1835 pub user_token_account : & ' a AccountInfo < ' a > ,
1936 pub token_billing_config : & ' a AccountInfo < ' a > ,
@@ -387,23 +404,39 @@ pub mod token_admin_registry_writable {
387404 }
388405}
389406
407+ /// Returns true iff `a >= b` when both are interpreted as big-endian uint256.
408+ fn ge_be_u256 ( a : & [ u8 ; 32 ] , b : & [ u8 ; 32 ] ) -> bool {
409+ // Big-endian uint256 compare == lexicographic compare
410+ for i in 0 ..32 {
411+ match a[ i] . cmp ( & b[ i] ) {
412+ std:: cmp:: Ordering :: Greater => return true ,
413+ std:: cmp:: Ordering :: Less => return false ,
414+ std:: cmp:: Ordering :: Equal => { } // continue
415+ }
416+ }
417+ true // equal
418+ }
419+
390420// address validation helpers based on the chain family selector
391421pub fn validate_evm_address ( address : & [ u8 ] ) -> Result < ( ) > {
392422 require_eq ! ( address. len( ) , 32 , CommonCcipError :: InvalidEVMAddress ) ;
393423
424+ let addr_32: & [ u8 ; 32 ] = address
425+ . try_into ( )
426+ . expect ( "slice length is guaranteed to be 32" ) ;
427+
428+ require ! (
429+ ge_be_u256( addr_32, & EVM_PRECOMPILE_SPACE ) ,
430+ CommonCcipError :: InvalidEVMAddress
431+ ) ;
432+
394433 let address: U256 = U256 :: from_be_bytes (
395434 address
396435 . try_into ( )
397436 . map_err ( |_| CommonCcipError :: InvalidEncoding ) ?,
398437 ) ;
399438 require ! ( address <= U160_MAX , CommonCcipError :: InvalidEVMAddress ) ;
400- if let Ok ( small_address) = TryInto :: < u32 > :: try_into ( address) {
401- require_gte ! (
402- small_address,
403- EVM_PRECOMPILE_SPACE ,
404- CommonCcipError :: InvalidEVMAddress
405- )
406- } ;
439+
407440 Ok ( ( ) )
408441}
409442
@@ -443,14 +476,32 @@ pub fn validate_tvm_address(address: &[u8]) -> Result<()> {
443476pub fn validate_aptos_address ( address : & [ u8 ] ) -> Result < ( ) > {
444477 require_eq ! ( address. len( ) , 32 , CommonCcipError :: InvalidAptosAddress ) ;
445478
479+ let addr_32: & [ u8 ; 32 ] = address
480+ . try_into ( )
481+ . expect ( "slice length is guaranteed to be 32" ) ;
482+
446483 require ! (
447- address . iter ( ) . any ( |b| * b != 0 ) ,
484+ ge_be_u256 ( addr_32 , & APTOS_PRECOMPILE_SPACE ) ,
448485 CommonCcipError :: InvalidAptosAddress
449486 ) ;
450487
451488 Ok ( ( ) )
452489}
453490
491+ pub fn validate_sui_address ( address : & [ u8 ] ) -> Result < ( ) > {
492+ require_eq ! ( address. len( ) , 32 , CommonCcipError :: InvalidSuiAddress ) ;
493+
494+ let addr_32: & [ u8 ; 32 ] = address
495+ . try_into ( )
496+ . expect ( "slice length is guaranteed to be 32" ) ;
497+
498+ require ! (
499+ ge_be_u256( addr_32, & SUI_PRECOMPILE_SPACE ) ,
500+ CommonCcipError :: InvalidSuiAddress
501+ ) ;
502+
503+ Ok ( ( ) )
504+ }
454505#[ cfg( test) ]
455506mod tests {
456507 use super :: * ;
@@ -552,6 +603,24 @@ mod tests {
552603 assert ! ( result. is_err( ) ) ;
553604 }
554605
606+ // -----------------------------
607+ // Helpers for address tests
608+ // -----------------------------
609+
610+ fn u256_to_32_be ( x : U256 ) -> [ u8 ; 32 ] {
611+ x. to_be_bytes ( )
612+ }
613+
614+ fn be32_with_last_byte ( v : u8 ) -> [ u8 ; 32 ] {
615+ let mut a = [ 0u8 ; 32 ] ;
616+ a[ 31 ] = v;
617+ a
618+ }
619+
620+ // -----------------------------
621+ // TON / TVM address tests
622+ // -----------------------------
623+
555624 #[ test]
556625 fn validate_tvm_address_accepts_len_36_nonzero_account_id ( ) {
557626 // 36-byte TON address raw format:
@@ -584,9 +653,18 @@ mod tests {
584653 ) ;
585654 }
586655
656+ // -----------------------------
657+ // Aptos address tests
658+ // -----------------------------
659+
587660 #[ test]
588- fn validate_aptos_address_accepts_len_32_nonzero ( ) {
589- let addr = [ 1u8 ; 32 ] ;
661+ fn validate_aptos_address_accepts_valid_ge_min ( ) {
662+ // exactly min
663+ validate_aptos_address ( & APTOS_PRECOMPILE_SPACE ) . unwrap ( ) ;
664+
665+ // > min
666+ let mut addr = APTOS_PRECOMPILE_SPACE ;
667+ addr[ 31 ] = 0x0c ;
590668 validate_aptos_address ( & addr) . unwrap ( ) ;
591669 }
592670
@@ -600,11 +678,169 @@ mod tests {
600678 }
601679
602680 #[ test]
603- fn validate_aptos_address_rejects_all_zero ( ) {
681+ fn validate_aptos_address_rejects_zero_address ( ) {
604682 let addr = [ 0u8 ; 32 ] ;
605683 assert_eq ! (
606684 validate_aptos_address( & addr) . unwrap_err( ) ,
607685 CommonCcipError :: InvalidAptosAddress . into( )
608686 ) ;
609687 }
688+
689+ #[ test]
690+ fn validate_aptos_address_rejects_lower_than_min ( ) {
691+ // 0x0a < 0x0b
692+ let addr = be32_with_last_byte ( 0x0a ) ;
693+ assert_eq ! (
694+ validate_aptos_address( & addr) . unwrap_err( ) ,
695+ CommonCcipError :: InvalidAptosAddress . into( )
696+ ) ;
697+ }
698+
699+ // -----------------------------
700+ // Sui address tests
701+ // -----------------------------
702+
703+ #[ test]
704+ fn validate_sui_address_accepts_valid_ge_min ( ) {
705+ // exactly min
706+ validate_sui_address ( & SUI_PRECOMPILE_SPACE ) . unwrap ( ) ;
707+
708+ // > min (increase last byte, staying >=)
709+ let mut addr = SUI_PRECOMPILE_SPACE ;
710+ addr[ 31 ] = 0xea ;
711+ validate_sui_address ( & addr) . unwrap ( ) ;
712+ }
713+
714+ #[ test]
715+ fn validate_sui_address_rejects_wrong_len ( ) {
716+ let addr = [ 1u8 ; 31 ] ;
717+ assert_eq ! (
718+ validate_sui_address( & addr) . unwrap_err( ) ,
719+ CommonCcipError :: InvalidSuiAddress . into( )
720+ ) ;
721+ }
722+
723+ #[ test]
724+ fn validate_sui_address_rejects_zero_address ( ) {
725+ let addr = [ 0u8 ; 32 ] ;
726+ assert_eq ! (
727+ validate_sui_address( & addr) . unwrap_err( ) ,
728+ CommonCcipError :: InvalidSuiAddress . into( )
729+ ) ;
730+ }
731+
732+ #[ test]
733+ fn validate_sui_address_rejects_lower_than_min ( ) {
734+ // Make something just below 0xdee9: 0xdee8
735+ let mut addr = SUI_PRECOMPILE_SPACE ;
736+ addr[ 31 ] = 0xe8 ;
737+ assert_eq ! (
738+ validate_sui_address( & addr) . unwrap_err( ) ,
739+ CommonCcipError :: InvalidSuiAddress . into( )
740+ ) ;
741+ }
742+
743+ // -----------------------------
744+ // EVM address tests
745+ // -----------------------------
746+
747+ #[ test]
748+ fn validate_evm_address_accepts_valid_ge_min_and_le_u160max ( ) {
749+ // exactly min (1024)
750+ validate_evm_address ( & EVM_PRECOMPILE_SPACE ) . unwrap ( ) ;
751+
752+ // > min but still small
753+ let mut addr = EVM_PRECOMPILE_SPACE ;
754+ addr[ 31 ] = 0x01 ; // 0x0401
755+ validate_evm_address ( & addr) . unwrap ( ) ;
756+
757+ // a high-but-valid 160-bit address: U160_MAX itself
758+ let high = u256_to_32_be ( U160_MAX ) ;
759+ validate_evm_address ( & high) . unwrap ( ) ;
760+ }
761+
762+ #[ test]
763+ fn validate_evm_address_rejects_wrong_len ( ) {
764+ let addr = [ 1u8 ; 31 ] ;
765+ assert_eq ! (
766+ validate_evm_address( & addr) . unwrap_err( ) ,
767+ CommonCcipError :: InvalidEVMAddress . into( )
768+ ) ;
769+ }
770+
771+ #[ test]
772+ fn validate_evm_address_rejects_zero_address ( ) {
773+ let addr = [ 0u8 ; 32 ] ;
774+ assert_eq ! (
775+ validate_evm_address( & addr) . unwrap_err( ) ,
776+ CommonCcipError :: InvalidEVMAddress . into( )
777+ ) ;
778+ }
779+
780+ #[ test]
781+ fn validate_evm_address_rejects_lower_than_min ( ) {
782+ // 1023 (0x03ff) is below 1024
783+ let addr = [
784+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
785+ 0 , 0x03 , 0xff ,
786+ ] ;
787+ assert_eq ! (
788+ validate_evm_address( & addr) . unwrap_err( ) ,
789+ CommonCcipError :: InvalidEVMAddress . into( )
790+ ) ;
791+
792+ // also check something very small like 1
793+ let addr = be32_with_last_byte ( 1 ) ;
794+ assert_eq ! (
795+ validate_evm_address( & addr) . unwrap_err( ) ,
796+ CommonCcipError :: InvalidEVMAddress . into( )
797+ ) ;
798+ }
799+
800+ #[ test]
801+ fn validate_evm_address_rejects_greater_than_u160max ( ) {
802+ // U160_MAX + 1 should fail the <= U160_MAX check
803+ let too_big = u256_to_32_be ( U160_MAX + U256 :: new ( 1 ) ) ;
804+ assert_eq ! (
805+ validate_evm_address( & too_big) . unwrap_err( ) ,
806+ CommonCcipError :: InvalidEVMAddress . into( )
807+ ) ;
808+
809+ // also fail for something with non-zero in the top 96 bits (definitely > u160 max)
810+ let mut definitely_too_big = [ 0u8 ; 32 ] ;
811+ definitely_too_big[ 0 ] = 1 ; // sets a high-order byte
812+ definitely_too_big[ 31 ] = 0x10 ; // keep non-zero
813+ assert_eq ! (
814+ validate_evm_address( & definitely_too_big) . unwrap_err( ) ,
815+ CommonCcipError :: InvalidEVMAddress . into( )
816+ ) ;
817+ }
818+
819+ // -----------------------------
820+ // SVM address tests (Solana)
821+ // -----------------------------
822+
823+ #[ test]
824+ fn validate_svm_address_accepts_valid_pubkey_bytes ( ) {
825+ let pk = Pubkey :: new_unique ( ) ;
826+ validate_svm_address ( pk. as_ref ( ) , true ) . unwrap ( ) ;
827+ }
828+
829+ #[ test]
830+ fn validate_svm_address_rejects_wrong_len ( ) {
831+ let addr = [ 1u8 ; 31 ] ;
832+ assert_eq ! (
833+ validate_svm_address( & addr, true ) . unwrap_err( ) ,
834+ CommonCcipError :: InvalidSVMAddress . into( )
835+ ) ;
836+ }
837+
838+ #[ test]
839+ fn validate_svm_address_rejects_all_zero ( ) {
840+ let addr = [ 0u8 ; 32 ] ;
841+ assert_eq ! (
842+ validate_svm_address( & addr, true ) . unwrap_err( ) ,
843+ CommonCcipError :: InvalidSVMAddress . into( )
844+ ) ;
845+ }
610846}
0 commit comments