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