@@ -54,13 +54,13 @@ pub struct OracleSet<'a> {
5454
5555impl Model for OracleSet < ' _ > {
5656 fn get_errors ( & self ) -> XRPLModelResult < ( ) > {
57- validate_optional_length (
57+ validate_optional_blob (
5858 "provider" ,
5959 self . provider . as_deref ( ) ,
6060 MAX_ORACLE_PROVIDER_BYTES ,
6161 ) ?;
62- validate_optional_length ( "uri" , self . uri . as_deref ( ) , MAX_ORACLE_URI_BYTES ) ?;
63- validate_optional_length (
62+ validate_optional_blob ( "uri" , self . uri . as_deref ( ) , MAX_ORACLE_URI_BYTES ) ?;
63+ validate_optional_blob (
6464 "asset_class" ,
6565 self . asset_class . as_deref ( ) ,
6666 MAX_ORACLE_ASSET_CLASS_BYTES ,
@@ -104,20 +104,25 @@ impl Model for OracleSet<'_> {
104104 }
105105}
106106
107- fn validate_optional_length (
107+ fn validate_optional_blob (
108108 field : & ' static str ,
109109 value : Option < & str > ,
110- max : usize ,
110+ max_bytes : usize ,
111111) -> XRPLModelResult < ( ) > {
112- if let Some ( value) = value {
113- let found = value. len ( ) ;
114- if found > max {
115- return Err ( XRPLModelException :: ValueTooLong {
116- field : field. into ( ) ,
117- max,
118- found,
119- } ) ;
120- }
112+ let Some ( value) = value else {
113+ return Ok ( ( ) ) ;
114+ } ;
115+ let bytes = hex:: decode ( value) . map_err ( |_| XRPLModelException :: InvalidValue {
116+ field : field. into ( ) ,
117+ expected : "a hex-encoded Blob string" . into ( ) ,
118+ found : value. into ( ) ,
119+ } ) ?;
120+ if bytes. len ( ) > max_bytes {
121+ return Err ( XRPLModelException :: ValueTooLong {
122+ field : field. into ( ) ,
123+ max : max_bytes,
124+ found : bytes. len ( ) ,
125+ } ) ;
121126 }
122127 Ok ( ( ) )
123128}
@@ -245,8 +250,8 @@ mod tests {
245250 ..Default :: default ( )
246251 } ,
247252 oracle_document_id : 1 ,
248- provider : Some ( "chainlink " . into ( ) ) ,
249- uri : Some ( "https://example.com/oracle1 " . into ( ) ) ,
253+ provider : Some ( "636861696E6C696E6B " . into ( ) ) ,
254+ uri : Some ( "68747470733A2F2F6578616D706C652E636F6D2F6F7261636C6531 " . into ( ) ) ,
250255 asset_class : Some ( "63757272656E6379" . into ( ) ) ,
251256 last_update_time : 743609014 ,
252257 price_data_series : vec ! [ PriceData {
@@ -576,7 +581,7 @@ mod tests {
576581
577582 #[ test]
578583 fn test_scale_too_high_rejected ( ) {
579- // Per rippled, `scale` must be in the inclusive range 0..=20 .
584+ // Per rippled, `scale` must be in the inclusive range 0..=10 .
580585 let oracle_set = OracleSet {
581586 common_fields : CommonFields {
582587 account : "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW" . into ( ) ,
@@ -589,23 +594,23 @@ mod tests {
589594 base_asset: "EUR" . to_string( ) ,
590595 quote_asset: "USD" . to_string( ) ,
591596 asset_price: Some ( "100" . to_string( ) ) ,
592- scale: Some ( 21 ) ,
597+ scale: Some ( 11 ) ,
593598 } ] ) ;
594599
595600 let err = oracle_set. get_errors ( ) . unwrap_err ( ) ;
596601 assert_eq ! (
597602 err,
598603 XRPLModelException :: ValueTooHigh {
599604 field: "scale" . into( ) ,
600- max: 20 ,
601- found: 21 ,
605+ max: 10 ,
606+ found: 11 ,
602607 }
603608 ) ;
604609 }
605610
606611 #[ test]
607612 fn test_scale_at_max_ok ( ) {
608- // Boundary: scale = 20 is explicitly permitted.
613+ // Boundary: scale = 10 is explicitly permitted.
609614 let oracle_set = OracleSet {
610615 common_fields : CommonFields {
611616 account : "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW" . into ( ) ,
@@ -618,12 +623,35 @@ mod tests {
618623 base_asset: "EUR" . to_string( ) ,
619624 quote_asset: "USD" . to_string( ) ,
620625 asset_price: Some ( "100" . to_string( ) ) ,
621- scale: Some ( 20 ) ,
626+ scale: Some ( 10 ) ,
622627 } ] ) ;
623628
624629 assert ! ( oracle_set. get_errors( ) . is_ok( ) ) ;
625630 }
626631
632+ #[ test]
633+ fn test_asset_price_and_scale_must_be_paired ( ) {
634+ let oracle_set = OracleSet {
635+ common_fields : CommonFields {
636+ account : "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW" . into ( ) ,
637+ transaction_type : TransactionType :: OracleSet ,
638+ ..Default :: default ( )
639+ } ,
640+ ..Default :: default ( )
641+ }
642+ . with_price_data_series ( vec ! [ PriceData {
643+ base_asset: "XRP" . to_string( ) ,
644+ quote_asset: "USD" . to_string( ) ,
645+ asset_price: Some ( "100" . to_string( ) ) ,
646+ scale: None ,
647+ } ] ) ;
648+
649+ assert ! ( matches!(
650+ oracle_set. get_errors( ) . unwrap_err( ) ,
651+ XRPLModelException :: InvalidValue { ref field, .. } if field == "price_data"
652+ ) ) ;
653+ }
654+
627655 #[ test]
628656 fn test_invalid_base_asset_rejected ( ) {
629657 // A 4-character code is neither a valid ISO code nor a 40-char hex.
@@ -699,7 +727,7 @@ mod tests {
699727 transaction_type : TransactionType :: OracleSet ,
700728 ..Default :: default ( )
701729 } ,
702- provider : Some ( alloc :: format! ( "{}x" , "a" . repeat( MAX_ORACLE_PROVIDER_BYTES ) ) . into ( ) ) ,
730+ provider : Some ( "AA" . repeat ( MAX_ORACLE_PROVIDER_BYTES + 1 ) . into ( ) ) ,
703731 price_data_series : vec ! [ PriceData {
704732 base_asset: "XRP" . to_string( ) ,
705733 quote_asset: "USD" . to_string( ) ,
@@ -716,6 +744,53 @@ mod tests {
716744 ) ) ;
717745 }
718746
747+ #[ test]
748+ fn test_oracle_metadata_must_be_hex ( ) {
749+ let oracle_set = OracleSet {
750+ common_fields : CommonFields {
751+ account : "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW" . into ( ) ,
752+ transaction_type : TransactionType :: OracleSet ,
753+ ..Default :: default ( )
754+ } ,
755+ provider : Some ( "chainlink" . into ( ) ) ,
756+ price_data_series : vec ! [ PriceData {
757+ base_asset: "XRP" . to_string( ) ,
758+ quote_asset: "USD" . to_string( ) ,
759+ asset_price: Some ( "100" . to_string( ) ) ,
760+ scale: Some ( 1 ) ,
761+ } ] ,
762+ ..Default :: default ( )
763+ } ;
764+
765+ assert ! ( matches!(
766+ oracle_set. get_errors( ) . unwrap_err( ) ,
767+ XRPLModelException :: InvalidValue { ref field, .. } if field == "provider"
768+ ) ) ;
769+ }
770+
771+ #[ test]
772+ fn test_asset_price_too_high_rejected ( ) {
773+ let oracle_set = OracleSet {
774+ common_fields : CommonFields {
775+ account : "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW" . into ( ) ,
776+ transaction_type : TransactionType :: OracleSet ,
777+ ..Default :: default ( )
778+ } ,
779+ ..Default :: default ( )
780+ }
781+ . with_price_data_series ( vec ! [ PriceData {
782+ base_asset: "XRP" . to_string( ) ,
783+ quote_asset: "USD" . to_string( ) ,
784+ asset_price: Some ( "8000000000000000" . to_string( ) ) ,
785+ scale: Some ( 1 ) ,
786+ } ] ) ;
787+
788+ assert ! ( matches!(
789+ oracle_set. get_errors( ) . unwrap_err( ) ,
790+ XRPLModelException :: InvalidValue { ref field, .. } if field == "asset_price"
791+ ) ) ;
792+ }
793+
719794 #[ test]
720795 fn test_duplicate_price_data_pair_rejected ( ) {
721796 let oracle_set = OracleSet {
0 commit comments