@@ -183,6 +183,14 @@ impl RefundBuilder {
183183 }
184184}
185185
186+ #[ cfg( test) ]
187+ impl RefundBuilder {
188+ fn features_unchecked ( mut self , features : InvoiceRequestFeatures ) -> Self {
189+ self . refund . features = features;
190+ self
191+ }
192+ }
193+
186194/// A `Refund` is a request to send an `Invoice` without a preceding [`Offer`].
187195///
188196/// Typically, after an invoice is paid, the recipient may publish a refund allowing the sender to
@@ -480,21 +488,21 @@ impl core::fmt::Display for Refund {
480488
481489#[ cfg( test) ]
482490mod tests {
483- use super :: { Refund , RefundBuilder } ;
491+ use super :: { Refund , RefundBuilder , RefundTlvStreamRef } ;
484492
485493 use bitcoin:: blockdata:: constants:: ChainHash ;
486494 use bitcoin:: network:: constants:: Network ;
487495 use bitcoin:: secp256k1:: { KeyPair , PublicKey , Secp256k1 , SecretKey } ;
488496 use core:: convert:: TryFrom ;
489497 use core:: time:: Duration ;
490- use crate :: ln:: features:: InvoiceRequestFeatures ;
491- use crate :: ln:: msgs:: MAX_VALUE_MSAT ;
498+ use crate :: ln:: features:: { InvoiceRequestFeatures , OfferFeatures } ;
499+ use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
492500 use crate :: offers:: invoice_request:: InvoiceRequestTlvStreamRef ;
493501 use crate :: offers:: offer:: OfferTlvStreamRef ;
494- use crate :: offers:: parse:: SemanticError ;
502+ use crate :: offers:: parse:: { ParseError , SemanticError } ;
495503 use crate :: offers:: payer:: PayerTlvStreamRef ;
496504 use crate :: onion_message:: { BlindedHop , BlindedPath } ;
497- use crate :: util:: ser:: Writeable ;
505+ use crate :: util:: ser:: { BigSize , Writeable } ;
498506 use crate :: util:: string:: PrintableString ;
499507
500508 fn payer_pubkey ( ) -> PublicKey {
@@ -511,6 +519,18 @@ mod tests {
511519 SecretKey :: from_slice ( & [ byte; 32 ] ) . unwrap ( )
512520 }
513521
522+ trait ToBytes {
523+ fn to_bytes ( & self ) -> Vec < u8 > ;
524+ }
525+
526+ impl < ' a > ToBytes for RefundTlvStreamRef < ' a > {
527+ fn to_bytes ( & self ) -> Vec < u8 > {
528+ let mut buffer = Vec :: new ( ) ;
529+ self . write ( & mut buffer) . unwrap ( ) ;
530+ buffer
531+ }
532+ }
533+
514534 #[ test]
515535 fn builds_refund_with_defaults ( ) {
516536 let refund = RefundBuilder :: new ( "foo" . into ( ) , vec ! [ 1 ; 32 ] , payer_pubkey ( ) , 1000 ) . unwrap ( )
@@ -700,4 +720,229 @@ mod tests {
700720 assert_eq ! ( refund. payer_note( ) , Some ( PrintableString ( "baz" ) ) ) ;
701721 assert_eq ! ( tlv_stream. payer_note, Some ( & String :: from( "baz" ) ) ) ;
702722 }
723+
724+ #[ test]
725+ fn parses_refund_with_metadata ( ) {
726+ let refund = RefundBuilder :: new ( "foo" . into ( ) , vec ! [ 1 ; 32 ] , payer_pubkey ( ) , 1000 ) . unwrap ( )
727+ . build ( ) . unwrap ( ) ;
728+ if let Err ( e) = refund. to_string ( ) . parse :: < Refund > ( ) {
729+ panic ! ( "error parsing refund: {:?}" , e) ;
730+ }
731+
732+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
733+ tlv_stream. 0 . metadata = None ;
734+
735+ match Refund :: try_from ( tlv_stream. to_bytes ( ) ) {
736+ Ok ( _) => panic ! ( "expected error" ) ,
737+ Err ( e) => {
738+ assert_eq ! ( e, ParseError :: InvalidSemantics ( SemanticError :: MissingPayerMetadata ) ) ;
739+ } ,
740+ }
741+ }
742+
743+ #[ test]
744+ fn parses_refund_with_description ( ) {
745+ let refund = RefundBuilder :: new ( "foo" . into ( ) , vec ! [ 1 ; 32 ] , payer_pubkey ( ) , 1000 ) . unwrap ( )
746+ . build ( ) . unwrap ( ) ;
747+ if let Err ( e) = refund. to_string ( ) . parse :: < Refund > ( ) {
748+ panic ! ( "error parsing refund: {:?}" , e) ;
749+ }
750+
751+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
752+ tlv_stream. 1 . description = None ;
753+
754+ match Refund :: try_from ( tlv_stream. to_bytes ( ) ) {
755+ Ok ( _) => panic ! ( "expected error" ) ,
756+ Err ( e) => {
757+ assert_eq ! ( e, ParseError :: InvalidSemantics ( SemanticError :: MissingDescription ) ) ;
758+ } ,
759+ }
760+ }
761+
762+ #[ test]
763+ fn parses_refund_with_amount ( ) {
764+ let refund = RefundBuilder :: new ( "foo" . into ( ) , vec ! [ 1 ; 32 ] , payer_pubkey ( ) , 1000 ) . unwrap ( )
765+ . build ( ) . unwrap ( ) ;
766+ if let Err ( e) = refund. to_string ( ) . parse :: < Refund > ( ) {
767+ panic ! ( "error parsing refund: {:?}" , e) ;
768+ }
769+
770+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
771+ tlv_stream. 2 . amount = None ;
772+
773+ match Refund :: try_from ( tlv_stream. to_bytes ( ) ) {
774+ Ok ( _) => panic ! ( "expected error" ) ,
775+ Err ( e) => {
776+ assert_eq ! ( e, ParseError :: InvalidSemantics ( SemanticError :: MissingAmount ) ) ;
777+ } ,
778+ }
779+
780+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
781+ tlv_stream. 2 . amount = Some ( MAX_VALUE_MSAT + 1 ) ;
782+
783+ match Refund :: try_from ( tlv_stream. to_bytes ( ) ) {
784+ Ok ( _) => panic ! ( "expected error" ) ,
785+ Err ( e) => {
786+ assert_eq ! ( e, ParseError :: InvalidSemantics ( SemanticError :: InvalidAmount ) ) ;
787+ } ,
788+ }
789+ }
790+
791+ #[ test]
792+ fn parses_refund_with_payer_id ( ) {
793+ let refund = RefundBuilder :: new ( "foo" . into ( ) , vec ! [ 1 ; 32 ] , payer_pubkey ( ) , 1000 ) . unwrap ( )
794+ . build ( ) . unwrap ( ) ;
795+ if let Err ( e) = refund. to_string ( ) . parse :: < Refund > ( ) {
796+ panic ! ( "error parsing refund: {:?}" , e) ;
797+ }
798+
799+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
800+ tlv_stream. 2 . payer_id = None ;
801+
802+ match Refund :: try_from ( tlv_stream. to_bytes ( ) ) {
803+ Ok ( _) => panic ! ( "expected error" ) ,
804+ Err ( e) => {
805+ assert_eq ! ( e, ParseError :: InvalidSemantics ( SemanticError :: MissingPayerId ) ) ;
806+ } ,
807+ }
808+ }
809+
810+ #[ test]
811+ fn parses_refund_with_optional_fields ( ) {
812+ let past_expiry = Duration :: from_secs ( 0 ) ;
813+ let paths = vec ! [
814+ BlindedPath {
815+ introduction_node_id: pubkey( 40 ) ,
816+ blinding_point: pubkey( 41 ) ,
817+ blinded_hops: vec![
818+ BlindedHop { blinded_node_id: pubkey( 43 ) , encrypted_payload: vec![ 0 ; 43 ] } ,
819+ BlindedHop { blinded_node_id: pubkey( 44 ) , encrypted_payload: vec![ 0 ; 44 ] } ,
820+ ] ,
821+ } ,
822+ BlindedPath {
823+ introduction_node_id: pubkey( 40 ) ,
824+ blinding_point: pubkey( 41 ) ,
825+ blinded_hops: vec![
826+ BlindedHop { blinded_node_id: pubkey( 45 ) , encrypted_payload: vec![ 0 ; 45 ] } ,
827+ BlindedHop { blinded_node_id: pubkey( 46 ) , encrypted_payload: vec![ 0 ; 46 ] } ,
828+ ] ,
829+ } ,
830+ ] ;
831+
832+ let refund = RefundBuilder :: new ( "foo" . into ( ) , vec ! [ 1 ; 32 ] , payer_pubkey ( ) , 1000 ) . unwrap ( )
833+ . absolute_expiry ( past_expiry)
834+ . issuer ( "bar" . into ( ) )
835+ . path ( paths[ 0 ] . clone ( ) )
836+ . path ( paths[ 1 ] . clone ( ) )
837+ . chain ( Network :: Testnet )
838+ . features_unchecked ( InvoiceRequestFeatures :: unknown ( ) )
839+ . payer_note ( "baz" . into ( ) )
840+ . build ( )
841+ . unwrap ( ) ;
842+ match refund. to_string ( ) . parse :: < Refund > ( ) {
843+ Ok ( refund) => {
844+ assert_eq ! ( refund. absolute_expiry( ) , Some ( past_expiry) ) ;
845+ #[ cfg( feature = "std" ) ]
846+ assert ! ( refund. is_expired( ) ) ;
847+ assert_eq ! ( refund. paths( ) , & paths[ ..] ) ;
848+ assert_eq ! ( refund. issuer( ) , Some ( PrintableString ( "bar" ) ) ) ;
849+ assert_eq ! ( refund. chain( ) , ChainHash :: using_genesis_block( Network :: Testnet ) ) ;
850+ assert_eq ! ( refund. features( ) , & InvoiceRequestFeatures :: unknown( ) ) ;
851+ assert_eq ! ( refund. payer_note( ) , Some ( PrintableString ( "baz" ) ) ) ;
852+ } ,
853+ Err ( e) => panic ! ( "error parsing refund: {:?}" , e) ,
854+ }
855+ }
856+
857+ #[ test]
858+ fn fails_parsing_refund_with_unexpected_fields ( ) {
859+ let refund = RefundBuilder :: new ( "foo" . into ( ) , vec ! [ 1 ; 32 ] , payer_pubkey ( ) , 1000 ) . unwrap ( )
860+ . build ( ) . unwrap ( ) ;
861+ if let Err ( e) = refund. to_string ( ) . parse :: < Refund > ( ) {
862+ panic ! ( "error parsing refund: {:?}" , e) ;
863+ }
864+
865+ let chains = vec ! [ ChainHash :: using_genesis_block( Network :: Testnet ) ] ;
866+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
867+ tlv_stream. 1 . chains = Some ( & chains) ;
868+
869+ match Refund :: try_from ( tlv_stream. to_bytes ( ) ) {
870+ Ok ( _) => panic ! ( "expected error" ) ,
871+ Err ( e) => {
872+ assert_eq ! ( e, ParseError :: InvalidSemantics ( SemanticError :: UnexpectedChain ) ) ;
873+ } ,
874+ }
875+
876+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
877+ tlv_stream. 1 . currency = Some ( & b"USD" ) ;
878+ tlv_stream. 1 . amount = Some ( 1000 ) ;
879+
880+ match Refund :: try_from ( tlv_stream. to_bytes ( ) ) {
881+ Ok ( _) => panic ! ( "expected error" ) ,
882+ Err ( e) => {
883+ assert_eq ! ( e, ParseError :: InvalidSemantics ( SemanticError :: UnexpectedAmount ) ) ;
884+ } ,
885+ }
886+
887+ let features = OfferFeatures :: unknown ( ) ;
888+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
889+ tlv_stream. 1 . features = Some ( & features) ;
890+
891+ match Refund :: try_from ( tlv_stream. to_bytes ( ) ) {
892+ Ok ( _) => panic ! ( "expected error" ) ,
893+ Err ( e) => {
894+ assert_eq ! ( e, ParseError :: InvalidSemantics ( SemanticError :: UnexpectedFeatures ) ) ;
895+ } ,
896+ }
897+
898+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
899+ tlv_stream. 1 . quantity_max = Some ( 10 ) ;
900+
901+ match Refund :: try_from ( tlv_stream. to_bytes ( ) ) {
902+ Ok ( _) => panic ! ( "expected error" ) ,
903+ Err ( e) => {
904+ assert_eq ! ( e, ParseError :: InvalidSemantics ( SemanticError :: UnexpectedQuantity ) ) ;
905+ } ,
906+ }
907+
908+ let node_id = payer_pubkey ( ) ;
909+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
910+ tlv_stream. 1 . node_id = Some ( & node_id) ;
911+
912+ match Refund :: try_from ( tlv_stream. to_bytes ( ) ) {
913+ Ok ( _) => panic ! ( "expected error" ) ,
914+ Err ( e) => {
915+ assert_eq ! ( e, ParseError :: InvalidSemantics ( SemanticError :: UnexpectedSigningPubkey ) ) ;
916+ } ,
917+ }
918+
919+ let mut tlv_stream = refund. as_tlv_stream ( ) ;
920+ tlv_stream. 2 . quantity = Some ( 10 ) ;
921+
922+ match Refund :: try_from ( tlv_stream. to_bytes ( ) ) {
923+ Ok ( _) => panic ! ( "expected error" ) ,
924+ Err ( e) => {
925+ assert_eq ! ( e, ParseError :: InvalidSemantics ( SemanticError :: UnexpectedQuantity ) ) ;
926+ } ,
927+ }
928+ }
929+
930+ #[ test]
931+ fn fails_parsing_refund_with_extra_tlv_records ( ) {
932+ let secp_ctx = Secp256k1 :: new ( ) ;
933+ let keys = KeyPair :: from_secret_key ( & secp_ctx, & SecretKey :: from_slice ( & [ 42 ; 32 ] ) . unwrap ( ) ) ;
934+ let refund = RefundBuilder :: new ( "foo" . into ( ) , vec ! [ 1 ; 32 ] , keys. public_key ( ) , 1000 ) . unwrap ( )
935+ . build ( ) . unwrap ( ) ;
936+
937+ let mut encoded_refund = Vec :: new ( ) ;
938+ refund. write ( & mut encoded_refund) . unwrap ( ) ;
939+ BigSize ( 1002 ) . write ( & mut encoded_refund) . unwrap ( ) ;
940+ BigSize ( 32 ) . write ( & mut encoded_refund) . unwrap ( ) ;
941+ [ 42u8 ; 32 ] . write ( & mut encoded_refund) . unwrap ( ) ;
942+
943+ match Refund :: try_from ( encoded_refund) {
944+ Ok ( _) => panic ! ( "expected error" ) ,
945+ Err ( e) => assert_eq ! ( e, ParseError :: Decode ( DecodeError :: InvalidValue ) ) ,
946+ }
947+ }
703948}
0 commit comments