@@ -222,6 +222,11 @@ impl VaultTestEnv {
222222 self . get_wbtc_token_client ( ) . balance ( & self . treasurer )
223223 }
224224
225+ /// Get vault contract's WBTC balance
226+ fn get_vault_wbtc_balance ( & self ) -> i128 {
227+ self . get_wbtc_token_client ( ) . balance ( & self . vault_addr )
228+ }
229+
225230 /// Get user's SolvBTC balance
226231 fn get_user_solvbtc_balance ( & self ) -> i128 {
227232 self . get_solvbtc_token_client ( ) . balance ( & self . user )
@@ -371,8 +376,13 @@ impl VaultTestEnv {
371376 let eip712_message = self . create_eip712_signature_message ( & message_hash_bytes) ;
372377 Self :: debug_print_bytes ( "EIP712 message" , & eip712_message) ;
373378
379+ // 3.5. Ed25519 requires an additional sha256 hash on the eip712_message
380+ let digest = self . env . crypto ( ) . sha256 ( & eip712_message) ;
381+ let digest_bytes: Bytes = digest. into ( ) ;
382+ Self :: debug_print_bytes ( "Final digest for Ed25519" , & digest_bytes) ;
383+
374384 // 4. Convert message to signable format
375- let message_vec = Self :: bytes_to_vec_for_signing ( & eip712_message ) ;
385+ let message_vec = Self :: bytes_to_vec_for_signing ( & digest_bytes ) ;
376386
377387 // 5. Get keypair and sign
378388 let ( signing_key, verifying_key) = Self :: create_real_keypair ( ) ;
@@ -455,6 +465,8 @@ impl VaultTestEnv {
455465 & nav,
456466 & request_hash,
457467 & signature,
468+ & 0u32 ,
469+ & 0u32 ,
458470 )
459471 }
460472}
@@ -741,7 +753,7 @@ fn test_complete_vault_withdraw_flow() {
741753 assert_eq ! ( domain_separator. len( ) , 32 ) ;
742754
743755 // Verify withdrawal settings
744- let withdraw_verifier = vault_client. get_withdraw_verifier ( ) ;
756+ let withdraw_verifier = vault_client. get_withdraw_verifier ( & 0u32 ) ;
745757 let withdraw_ratio = vault_client. get_withdraw_fee_ratio ( ) ;
746758 let withdraw_currency = vault_client. get_withdraw_currency ( ) ;
747759
@@ -758,7 +770,7 @@ fn test_complete_vault_withdraw_flow() {
758770 expected_verifier_bytes[ 0 ] = 0xDE ;
759771 expected_verifier_bytes[ 1 ] = 0xAD ;
760772 let expected_verifier = BytesN :: from_array ( & test_env. env , & expected_verifier_bytes) ;
761- assert_eq ! ( withdraw_verifier, expected_verifier) ;
773+ assert_eq ! ( withdraw_verifier, Some ( expected_verifier. into ( ) ) ) ;
762774 assert_eq ! ( withdraw_ratio, 100 ) ; // 1%
763775 assert ! ( withdraw_currency. is_some( ) ) ;
764776 assert_eq ! ( withdraw_currency. unwrap( ) , test_env. wbtc_token_addr) ;
@@ -818,15 +830,15 @@ fn test_withdraw_error_scenarios() {
818830 println ! ( "=== Test 3: Contract initialization state ===" ) ;
819831
820832 let admin = vault_client. get_admin ( ) ;
821- let withdraw_verifier = vault_client. get_withdraw_verifier ( ) ;
833+ let withdraw_verifier = vault_client. get_withdraw_verifier ( & 0u32 ) ;
822834 let withdraw_ratio = vault_client. get_withdraw_fee_ratio ( ) ;
823835
824836 assert_eq ! ( admin, test_env. admin) ;
825837 let mut expected_verifier_bytes = [ 0u8 ; 32 ] ;
826838 expected_verifier_bytes[ 0 ] = 0xDE ;
827839 expected_verifier_bytes[ 1 ] = 0xAD ;
828840 let expected_verifier = BytesN :: from_array ( & test_env. env , & expected_verifier_bytes) ;
829- assert_eq ! ( withdraw_verifier, expected_verifier) ;
841+ assert_eq ! ( withdraw_verifier, Some ( expected_verifier. into ( ) ) ) ;
830842 assert_eq ! ( withdraw_ratio, 100 ) ;
831843
832844 println ! ( "✓ Contract initialization state test passed" ) ;
@@ -887,7 +899,7 @@ fn test_withdraw_signature_validation_structure() {
887899
888900 // 6. Verify contract state
889901 let vault_client = test_env. get_vault_client ( ) ;
890- let withdraw_verifier = vault_client. get_withdraw_verifier ( ) ;
902+ let withdraw_verifier = vault_client. get_withdraw_verifier ( & 0u32 ) ;
891903 let withdraw_currency = vault_client. get_withdraw_currency ( ) ;
892904 let is_currency_supported = vault_client. is_currency_supported ( & test_env. wbtc_token_addr ) ;
893905
@@ -896,7 +908,8 @@ fn test_withdraw_signature_validation_structure() {
896908 expected_verifier_bytes[ 1 ] = 0xAD ;
897909 let expected_verifier = BytesN :: from_array ( & test_env. env , & expected_verifier_bytes) ;
898910 assert_eq ! (
899- withdraw_verifier, expected_verifier,
911+ withdraw_verifier,
912+ Some ( expected_verifier. into( ) ) ,
900913 "Verifier public key should match"
901914 ) ;
902915 assert ! (
@@ -946,6 +959,137 @@ fn test_withdraw_signature_validation_structure() {
946959 println ! ( " ✓ Complete withdrawal process structure verification passed" ) ;
947960}
948961
962+ #[ test]
963+ #[ should_panic]
964+ fn test_withdraw_secp256k1_invalid_signature_should_panic_integration ( ) {
965+ // 1) Initialize environment and relationships
966+ let test_env = VaultTestEnv :: new ( ) ;
967+ test_env. setup_relationships ( ) ;
968+
969+ // 2) Set secp256k1 verifier (65-byte uncompressed public key)
970+ let mut pubkey_bytes = [ 0u8 ; 65 ] ;
971+ pubkey_bytes[ 0 ] = 0x04 ;
972+ for i in 1 ..65 {
973+ pubkey_bytes[ i] = i as u8 ;
974+ }
975+ let secp_pub = Bytes :: from_slice ( & test_env. env , & pubkey_bytes) ;
976+ test_env
977+ . get_vault_client ( )
978+ . set_withdraw_verifier_by_admin ( & 1u32 , & secp_pub) ;
979+
980+ // 3) Mint shares to user
981+ let deposit_amount = 100_000_000i128 ;
982+ test_env. mint_wbtc_to_user ( deposit_amount) ;
983+ test_env. approve_vault_for_wbtc ( deposit_amount) ;
984+ test_env. set_nav_value ( 100_000_000i128 ) ;
985+ let minted = test_env. deposit ( deposit_amount) ;
986+
987+ // 4) Create withdraw_request, ensure status is Pending
988+ let shares = minted / 2 ;
989+ let request_hash = test_env. create_request_hash ( 9 ) ;
990+ test_env
991+ . get_vault_client ( )
992+ . withdraw_request ( & test_env. user , & shares, & request_hash) ;
993+
994+ // 5) Use invalid r||s and recovery_id=0 to trigger secp256k1 branch and expect panic
995+ let invalid_sig = BytesN :: < 64 > :: from_array ( & test_env. env , & [ 0u8 ; 64 ] ) ;
996+ test_env. get_vault_client ( ) . withdraw (
997+ & test_env. user ,
998+ & shares,
999+ & 100_000_000i128 ,
1000+ & request_hash,
1001+ & invalid_sig,
1002+ & 1u32 , // signature_type = secp256k1
1003+ & 0u32 , // recovery_id
1004+ ) ;
1005+ }
1006+
1007+ #[ test]
1008+ fn test_withdraw_ed25519_success_integration ( ) {
1009+ // 1) Initialize environment and relationships
1010+ let test_env = VaultTestEnv :: new ( ) ;
1011+ test_env. setup_relationships ( ) ;
1012+
1013+ // 2) Set ed25519 verifier = real public key used by signer
1014+ let ed_pub = test_env. get_real_public_key ( ) ; // Bytes(32)
1015+ test_env
1016+ . get_vault_client ( )
1017+ . set_withdraw_verifier_by_admin ( & 0u32 , & ed_pub) ;
1018+
1019+ // 3) Prepare balances: user deposit to get shares; treasurer provides liquidity
1020+ let nav = 100_000_000i128 ; // 1.0
1021+ let deposit_amount = 200_000_000i128 ; // 2 WBTC
1022+ test_env. mint_wbtc_to_user ( deposit_amount) ;
1023+ test_env. approve_vault_for_wbtc ( deposit_amount) ;
1024+ test_env. set_nav_value ( nav) ;
1025+ let minted = test_env. deposit ( deposit_amount) ;
1026+
1027+ // Treasurer liquidity >= expected withdraw amount
1028+ let liq = 200_000_000i128 ; // 2 WBTC
1029+ test_env. mint_wbtc_to_treasurer ( liq) ;
1030+ test_env. approve_vault_for_treasurer_wbtc ( liq) ;
1031+ test_env. treasurer_deposit_wbtc ( liq) ;
1032+
1033+ // 4) Create withdraw_request for shares
1034+ let shares = minted / 2 ; // withdraw half
1035+ let request_hash = test_env. create_request_hash ( 11 ) ;
1036+ test_env
1037+ . get_vault_client ( )
1038+ . withdraw_request ( & test_env. user , & shares, & request_hash) ;
1039+
1040+ // 5) Sign EIP712 message with real ed25519 key
1041+ let signature = test_env. sign_vault_withdraw_message (
1042+ & test_env. user ,
1043+ shares,
1044+ & test_env. wbtc_token_addr ,
1045+ nav,
1046+ & request_hash,
1047+ ) ;
1048+
1049+ // 6) Call withdraw and verify balances
1050+ let before_user_wbtc = test_env. get_user_wbtc_balance ( ) ;
1051+ let before_treas_wbtc = test_env. get_treasurer_wbtc_balance ( ) ;
1052+ let before_vault_wbtc = test_env. get_vault_wbtc_balance ( ) ;
1053+
1054+ // Compute expected amount using on-chain formula
1055+ let withdraw_dec = test_env. get_wbtc_token_client ( ) . decimals ( ) ;
1056+ let shares_dec = test_env. get_solvbtc_token_client ( ) . decimals ( ) ;
1057+ let nav_dec = test_env. get_oracle_client ( ) . get_nav_decimals ( ) ;
1058+ let pow10 = |n : u32 | -> i128 {
1059+ let mut x = 1i128 ;
1060+ for _ in 0 ..n {
1061+ x *= 10 ;
1062+ }
1063+ x
1064+ } ;
1065+ let amount = ( shares * nav * pow10 ( withdraw_dec) ) / ( pow10 ( nav_dec) * pow10 ( shares_dec) ) ;
1066+ let fee_bps = test_env. get_vault_client ( ) . get_withdraw_fee_ratio ( ) ;
1067+ let expected_fee = ( amount * fee_bps) / 10000 ;
1068+ let expected_after = amount - expected_fee;
1069+
1070+ let actual_after = test_env. get_vault_client ( ) . withdraw (
1071+ & test_env. user ,
1072+ & shares,
1073+ & nav,
1074+ & request_hash,
1075+ & signature,
1076+ & 0u32 , // signature_type=ed25519
1077+ & 0u32 , // recovery ignored
1078+ ) ;
1079+
1080+ let after_user_wbtc = test_env. get_user_wbtc_balance ( ) ;
1081+ let after_treas_wbtc = test_env. get_treasurer_wbtc_balance ( ) ;
1082+ let after_vault_wbtc = test_env. get_vault_wbtc_balance ( ) ;
1083+
1084+ assert_eq ! ( actual_after, expected_after) ;
1085+ assert_eq ! ( after_user_wbtc - before_user_wbtc, expected_after) ;
1086+ // Treasurer balance should remain the same since funds are transferred from vault contract
1087+ // not from treasurer directly
1088+ assert_eq ! ( before_treas_wbtc, after_treas_wbtc) ;
1089+ // Vault contract balance should decrease by the total amount (including fee)
1090+ assert_eq ! ( before_vault_wbtc - after_vault_wbtc, amount) ;
1091+ }
1092+
9491093#[ test]
9501094#[ should_panic]
9511095fn test_withdraw_with_invalid_signature_should_panic ( ) {
@@ -981,6 +1125,105 @@ fn test_withdraw_with_invalid_signature_should_panic() {
9811125 test_env. withdraw ( target_amount, nav_value, request_hash, invalid_signature) ;
9821126}
9831127
1128+ #[ test]
1129+ #[ should_panic( expected = "Error(Contract, #313)" ) ] // VaultError::Unauthorized = 313
1130+ fn test_withdraw_secp256k1_wrong_pubkey_should_panic ( ) {
1131+ // This test covers the Unauthorized error at line 1086 in vault.rs
1132+ // when recovered public key doesn't match expected public key
1133+
1134+ let test_env = VaultTestEnv :: new ( ) ;
1135+ test_env. setup_relationships ( ) ;
1136+
1137+ // 1. Set a specific secp256k1 public key in the contract
1138+ let mut expected_pubkey_bytes = [ 0u8 ; 65 ] ;
1139+ expected_pubkey_bytes[ 0 ] = 0x04 ; // uncompressed public key prefix
1140+ for i in 1 ..65 {
1141+ expected_pubkey_bytes[ i] = ( i * 2 ) as u8 ; // Some deterministic pattern
1142+ }
1143+ let expected_pub = Bytes :: from_slice ( & test_env. env , & expected_pubkey_bytes) ;
1144+ test_env
1145+ . get_vault_client ( )
1146+ . set_withdraw_verifier_by_admin ( & 1u32 , & expected_pub) ;
1147+
1148+ // 2. Prepare: deposit to get shares
1149+ let deposit_amount = 100_000_000i128 ;
1150+ test_env. mint_wbtc_to_user ( deposit_amount) ;
1151+ test_env. approve_vault_for_wbtc ( deposit_amount) ;
1152+ test_env. set_nav_value ( 100_000_000i128 ) ;
1153+ let minted = test_env. deposit ( deposit_amount) ;
1154+
1155+ // 3. Create withdraw request
1156+ let shares = minted / 2 ;
1157+ let request_hash = test_env. create_request_hash ( 42 ) ;
1158+ test_env
1159+ . get_vault_client ( )
1160+ . withdraw_request ( & test_env. user , & shares, & request_hash) ;
1161+
1162+ // 4. Prepare liquidity for withdrawal
1163+ let liquidity = 200_000_000i128 ;
1164+ test_env. mint_wbtc_to_treasurer ( liquidity) ;
1165+ test_env. approve_vault_for_treasurer_wbtc ( liquidity) ;
1166+ test_env. treasurer_deposit_wbtc ( liquidity) ;
1167+
1168+ // 5. Create a valid secp256k1 signature but with a DIFFERENT private key
1169+ // This will produce a valid signature that recovers to a different public key
1170+
1171+ // Create the withdraw message
1172+ let withdraw_message = test_env. create_vault_withdraw_message (
1173+ & test_env. user ,
1174+ shares,
1175+ & test_env. wbtc_token_addr ,
1176+ 100_000_000i128 ,
1177+ & request_hash,
1178+ ) ;
1179+
1180+ // Hash the message
1181+ let message_hash = test_env. env . crypto ( ) . sha256 ( & withdraw_message) ;
1182+
1183+ // Create EIP712 message
1184+ let domain_separator = Bytes :: from_slice (
1185+ & test_env. env ,
1186+ & [
1187+ 0xa5 , 0x93 , 0x7d , 0xb5 , 0x54 , 0xc5 , 0x13 , 0x4b , 0xd1 , 0xec , 0x9f , 0x43 , 0xfb , 0x96 ,
1188+ 0x12 , 0xab , 0xda , 0x34 , 0x02 , 0xed , 0xd9 , 0x7e , 0x43 , 0x3d , 0x26 , 0xd4 , 0x63 , 0xae ,
1189+ 0x26 , 0x55 , 0x18 , 0x0a ,
1190+ ] ,
1191+ ) ;
1192+ let prefix = Bytes :: from_slice ( & test_env. env , & [ 0x19 , 0x01 ] ) ;
1193+ let mut eip712_message = Bytes :: new ( & test_env. env ) ;
1194+ eip712_message. append ( & prefix) ;
1195+ eip712_message. append ( & domain_separator) ;
1196+ eip712_message. append ( & Bytes :: from ( message_hash) ) ;
1197+
1198+ // Create a valid secp256k1 signature that will recover to a different public key
1199+ // Using a known valid signature (r||s format, 64 bytes total)
1200+ // This signature is valid but will recover to a different public key than expected
1201+ let wrong_signature = BytesN :: < 64 > :: from_array (
1202+ & test_env. env ,
1203+ & [
1204+ // r (32 bytes) - valid secp256k1 r value
1205+ 0x8b , 0x9d , 0x7a , 0xe8 , 0x56 , 0x85 , 0x37 , 0x56 , 0x14 , 0x92 , 0xf6 , 0xd2 , 0x18 , 0x24 ,
1206+ 0xf2 , 0xb8 , 0x48 , 0x86 , 0x89 , 0xb4 , 0xae , 0x11 , 0x25 , 0x01 , 0x43 , 0x4f , 0x8d , 0x8c ,
1207+ 0xc5 , 0x11 , 0x50 , 0x66 , // s (32 bytes) - valid secp256k1 s value
1208+ 0x3f , 0xdd , 0x94 , 0xc9 , 0x7f , 0x25 , 0xe4 , 0x18 , 0x28 , 0xa3 , 0x7d , 0xfd , 0x81 , 0x9f ,
1209+ 0xc0 , 0xad , 0x23 , 0xc7 , 0xea , 0x67 , 0xe7 , 0xb3 , 0xe7 , 0x02 , 0xd9 , 0xd3 , 0xe2 , 0xf9 ,
1210+ 0x54 , 0x37 , 0x75 , 0x8e ,
1211+ ] ,
1212+ ) ;
1213+
1214+ // 6. This should panic with Unauthorized error because the recovered public key
1215+ // won't match the expected public key stored in the contract
1216+ test_env. get_vault_client ( ) . withdraw (
1217+ & test_env. user ,
1218+ & shares,
1219+ & 100_000_000i128 ,
1220+ & request_hash,
1221+ & wrong_signature,
1222+ & 1u32 , // signature_type = secp256k1
1223+ & 0u32 , // recovery_id
1224+ ) ;
1225+ }
1226+
9841227#[ test]
9851228fn test_withdraw_with_real_signature_success ( ) {
9861229 println ! ( "Starting test using real signature successful withdrawal process" ) ;
@@ -1000,7 +1243,7 @@ fn test_withdraw_with_real_signature_success() {
10001243 println ! ( ) ;
10011244
10021245 // Get verifier address set in contract
1003- let withdraw_verifier = test_env. get_vault_client ( ) . get_withdraw_verifier ( ) ;
1246+ let withdraw_verifier = test_env. get_vault_client ( ) . get_withdraw_verifier ( & 0u32 ) ;
10041247 println ! ( "Verifier address set in contract: {:?}" , withdraw_verifier) ;
10051248
10061249 // Prepare test data - first deposit
@@ -1504,7 +1747,7 @@ fn test_complete_withdraw_operation_flow() {
15041747
15051748 // Step 7: Verify withdrawal configuration
15061749 println ! ( "=== Step 7: Verify withdrawal configuration ===" ) ;
1507- let withdraw_verifier = vault_client. get_withdraw_verifier ( ) ;
1750+ let withdraw_verifier = vault_client. get_withdraw_verifier ( & 0u32 ) ;
15081751 let withdraw_fee_ratio = vault_client. get_withdraw_fee_ratio ( ) ;
15091752 let withdraw_fee_receiver = vault_client. get_withdraw_fee_receiver ( ) ;
15101753
@@ -1854,7 +2097,10 @@ fn test_vault_initialization_with_config() {
18542097 assert_eq ! ( vault_client. get_admin( ) , admin) ;
18552098 assert_eq ! ( vault_client. get_oracle( ) , oracle) ;
18562099 assert_eq ! ( vault_client. get_treasurer( ) , treasurer) ;
1857- assert_eq ! ( vault_client. get_withdraw_verifier( ) , withdraw_verifier) ;
2100+ assert_eq ! (
2101+ vault_client. get_withdraw_verifier( & 0u32 ) ,
2102+ Some ( withdraw_verifier. clone( ) . into( ) )
2103+ ) ;
18582104 assert_eq ! ( vault_client. get_withdraw_fee_ratio( ) , 150 ) ;
18592105 assert_eq ! (
18602106 vault_client. get_eip712_domain_name( ) ,
0 commit comments