@@ -2448,6 +2448,8 @@ pub enum OracleIntegrationKey {
24482448 VerificationStatus ( Symbol ) ,
24492449 /// Retry count for market verification
24502450 RetryCount ( Symbol ) ,
2451+ /// Configurable per-source weight
2452+ OracleWeight ( Address ) ,
24512453}
24522454
24532455/// Storage keys for oracle validation configuration.
@@ -2831,6 +2833,61 @@ impl OracleValidationConfigManager {
28312833pub struct OracleIntegrationManager ;
28322834
28332835impl OracleIntegrationManager {
2836+ /// Set configurable weight for a specific oracle source
2837+ pub fn set_oracle_weight (
2838+ env : & Env ,
2839+ admin : Address ,
2840+ oracle : Address ,
2841+ weight : u32 ,
2842+ ) -> Result < ( ) , Error > {
2843+ OracleWhitelist :: require_admin ( env, & admin) ?;
2844+ env. storage ( )
2845+ . persistent ( )
2846+ . set ( & OracleIntegrationKey :: OracleWeight ( oracle) , & weight) ;
2847+ Ok ( ( ) )
2848+ }
2849+
2850+ /// Get configured weight for an oracle source, defaults to 1
2851+ pub fn get_oracle_weight ( env : & Env , oracle : & Address ) -> u32 {
2852+ env. storage ( )
2853+ . persistent ( )
2854+ . get ( & OracleIntegrationKey :: OracleWeight ( oracle. clone ( ) ) )
2855+ . unwrap_or ( 1 )
2856+ }
2857+
2858+ /// Calculate the weighted median price safely
2859+ fn calculate_weighted_median (
2860+ _env : & Env ,
2861+ readings : & alloc:: vec:: Vec < ( i128 , u32 ) > ,
2862+ total_weight : u32 ,
2863+ ) -> i128 {
2864+ if readings. is_empty ( ) {
2865+ return 0 ;
2866+ }
2867+
2868+ let mut sorted: alloc:: vec:: Vec < ( i128 , u32 ) > =
2869+ alloc:: vec:: Vec :: with_capacity ( readings. len ( ) ) ;
2870+ for r in readings. iter ( ) {
2871+ sorted. push ( r. clone ( ) ) ;
2872+ }
2873+ sorted. sort_unstable_by ( |a, b| a. 0 . cmp ( & b. 0 ) ) ;
2874+
2875+ let target = total_weight / 2 ;
2876+ let mut accumulated: u32 = 0 ;
2877+
2878+ for ( price, weight) in sorted. iter ( ) {
2879+ accumulated = accumulated. saturating_add ( * weight) ;
2880+ if accumulated > target {
2881+ return * price;
2882+ }
2883+ }
2884+
2885+ if let Some ( last) = sorted. last ( ) {
2886+ last. 0
2887+ } else {
2888+ 0
2889+ }
2890+ }
28342891 /// Legacy defaults (actual validation uses OracleValidationConfigManager)
28352892 const MAX_DATA_AGE_SECONDS : u64 = 60 ;
28362893 /// Minimum confidence score required (not currently enforced here)
@@ -2938,8 +2995,8 @@ impl OracleIntegrationManager {
29382995 use crate :: events:: EventEmitter ;
29392996
29402997 let oracle_config = & market. oracle_config ;
2941- let mut successful_results : Vec < ( i128 , String ) > = Vec :: new ( env ) ;
2942- let mut total_price : i128 = 0 ;
2998+ let mut successful_readings : alloc :: vec :: Vec < ( i128 , u32 ) > = alloc :: vec :: Vec :: new ( ) ;
2999+ let mut total_weight : u32 = 0 ;
29433000 let mut sources_count: u32 = 0 ;
29443001 let mut last_error: Option < Error > = None ;
29453002
@@ -2955,17 +3012,12 @@ impl OracleIntegrationManager {
29553012 Ok ( price) => {
29563013 // Validate price is within acceptable range
29573014 if Self :: validate_price_range ( price) {
2958- // Determine outcome for this source
2959- let outcome = OracleUtils :: determine_outcome (
2960- price,
2961- oracle_config. threshold ,
2962- & oracle_config. comparison ,
2963- env,
2964- ) ?;
2965-
2966- successful_results. push_back ( ( price, outcome) ) ;
2967- total_price += price;
2968- sources_count += 1 ;
3015+ let weight = Self :: get_oracle_weight ( env, & oracle_address) ;
3016+ if weight > 0 {
3017+ successful_readings. push ( ( price, weight) ) ;
3018+ total_weight = total_weight. saturating_add ( weight) ;
3019+ sources_count += 1 ;
3020+ }
29693021 }
29703022 }
29713023 Err ( e) => {
@@ -2988,29 +3040,55 @@ impl OracleIntegrationManager {
29883040 return Err ( Error :: OracleUnavailable ) ;
29893041 }
29903042
2991- // Calculate average price
2992- let average_price = total_price / ( sources_count as i128 ) ;
3043+ // Calculate weighted median price
3044+ let median_price = Self :: calculate_weighted_median ( env , & successful_readings , total_weight ) ;
29933045
2994- // Calculate price variance (simplified - max deviation from average)
3046+ // Determine final outcome directly from the weighted median price
3047+ let final_outcome = OracleUtils :: determine_outcome (
3048+ median_price,
3049+ oracle_config. threshold ,
3050+ & oracle_config. comparison ,
3051+ env,
3052+ ) ?;
3053+
3054+ // Calculate agreement count for confidence score and legacy events
3055+ let mut agreement_count: u32 = 0 ;
3056+ let mut agreement_weight: u32 = 0 ;
3057+ for ( price, weight) in successful_readings. iter ( ) {
3058+ let outcome = OracleUtils :: determine_outcome (
3059+ * price,
3060+ oracle_config. threshold ,
3061+ & oracle_config. comparison ,
3062+ env,
3063+ ) ?;
3064+ if outcome == final_outcome {
3065+ agreement_count += 1 ;
3066+ agreement_weight = agreement_weight. saturating_add ( * weight) ;
3067+ }
3068+ }
3069+
3070+ // Calculate price variance (simplified - max deviation from median)
29953071 let mut max_deviation: i128 = 0 ;
2996- for ( price, _) in successful_results . iter ( ) {
2997- let deviation = if price > average_price {
2998- price - average_price
3072+ for ( price, _) in successful_readings . iter ( ) {
3073+ let deviation = if * price > median_price {
3074+ * price - median_price
29993075 } else {
3000- average_price - price
3076+ median_price - * price
30013077 } ;
30023078 if deviation > max_deviation {
30033079 max_deviation = deviation;
30043080 }
30053081 }
30063082
3007- // Determine consensus outcome
3008- let ( final_outcome, consensus_reached, agreement_count) =
3009- Self :: determine_consensus_outcome ( env, & successful_results) ?;
3083+ let agreement_percentage = if total_weight > 0 {
3084+ ( agreement_weight * 100 ) / total_weight
3085+ } else {
3086+ 0
3087+ } ;
30103088
3011- let agreement_percentage = ( agreement_count * 100 ) / sources_count;
3089+ // Check consensus threshold based on weight agreement
3090+ let consensus_reached = agreement_percentage >= Self :: DEFAULT_CONSENSUS_THRESHOLD ;
30123091
3013- // Check consensus threshold
30143092 if !consensus_reached {
30153093 EventEmitter :: emit_oracle_verification_failed (
30163094 env,
@@ -3030,23 +3108,23 @@ impl OracleIntegrationManager {
30303108 & final_outcome,
30313109 agreement_count,
30323110 sources_count,
3033- average_price ,
3111+ median_price ,
30343112 max_deviation,
30353113 ) ;
30363114
30373115 // Calculate confidence score based on agreement and price stability
30383116 let confidence_score = Self :: calculate_confidence_score (
30393117 agreement_percentage,
30403118 max_deviation,
3041- average_price ,
3119+ median_price ,
30423120 sources_count,
30433121 ) ;
30443122
30453123 // Build the oracle result
30463124 Ok ( crate :: types:: OracleResult {
30473125 market_id : market_id. clone ( ) ,
30483126 outcome : final_outcome,
3049- price : average_price ,
3127+ price : median_price ,
30503128 threshold : oracle_config. threshold ,
30513129 comparison : oracle_config. comparison . clone ( ) ,
30523130 provider : oracle_config. provider . clone ( ) ,
@@ -3101,39 +3179,7 @@ impl OracleIntegrationManager {
31013179 Ok ( price_data. price )
31023180 }
31033181
3104- /// Determine consensus outcome from multiple oracle results.
3105- fn determine_consensus_outcome (
3106- env : & Env ,
3107- results : & Vec < ( i128 , String ) > ,
3108- ) -> Result < ( String , bool , u32 ) , Error > {
3109- if results. is_empty ( ) {
3110- return Err ( Error :: OracleUnavailable ) ;
3111- }
3112-
3113- // Count outcomes
3114- let mut yes_count: u32 = 0 ;
3115- let mut no_count: u32 = 0 ;
3116-
3117- for ( _, outcome) in results. iter ( ) {
3118- if outcome == String :: from_str ( env, "yes" ) {
3119- yes_count += 1 ;
3120- } else {
3121- no_count += 1 ;
3122- }
3123- }
3124-
3125- let total = results. len ( ) as u32 ;
3126- let ( final_outcome, agreement_count) = if yes_count >= no_count {
3127- ( String :: from_str ( env, "yes" ) , yes_count)
3128- } else {
3129- ( String :: from_str ( env, "no" ) , no_count)
3130- } ;
3131-
3132- let agreement_percentage = ( agreement_count * 100 ) / total;
3133- let consensus_reached = agreement_percentage >= Self :: DEFAULT_CONSENSUS_THRESHOLD ;
31343182
3135- Ok ( ( final_outcome, consensus_reached, agreement_count) )
3136- }
31373183
31383184 /// Calculate confidence score based on multiple factors.
31393185 fn calculate_confidence_score (
@@ -3398,32 +3444,38 @@ mod oracle_integration_tests {
33983444 }
33993445
34003446 #[ test]
3401- fn test_determine_consensus_outcome ( ) {
3447+ fn test_calculate_weighted_median ( ) {
34023448 let env = Env :: default ( ) ;
34033449
3404- // All agree on "yes"
3405- let mut results: Vec < ( i128 , String ) > = Vec :: new ( & env) ;
3406- results. push_back ( ( 50_000_00 , String :: from_str ( & env, "yes" ) ) ) ;
3407- results. push_back ( ( 50_100_00 , String :: from_str ( & env, "yes" ) ) ) ;
3408- results. push_back ( ( 49_900_00 , String :: from_str ( & env, "yes" ) ) ) ;
3409-
3410- let ( outcome, consensus, count) =
3411- OracleIntegrationManager :: determine_consensus_outcome ( & env, & results) . unwrap ( ) ;
3412- assert_eq ! ( outcome, String :: from_str( & env, "yes" ) ) ;
3413- assert ! ( consensus) ;
3414- assert_eq ! ( count, 3 ) ;
3415-
3416- // Mixed results - 2 yes, 1 no (67% agreement)
3417- let mut mixed_results: Vec < ( i128 , String ) > = Vec :: new ( & env) ;
3418- mixed_results. push_back ( ( 50_000_00 , String :: from_str ( & env, "yes" ) ) ) ;
3419- mixed_results. push_back ( ( 50_100_00 , String :: from_str ( & env, "yes" ) ) ) ;
3420- mixed_results. push_back ( ( 49_000_00 , String :: from_str ( & env, "no" ) ) ) ;
3421-
3422- let ( outcome, consensus, count) =
3423- OracleIntegrationManager :: determine_consensus_outcome ( & env, & mixed_results) . unwrap ( ) ;
3424- assert_eq ! ( outcome, String :: from_str( & env, "yes" ) ) ;
3425- assert ! ( consensus) ; // 67% meets 66% threshold
3426- assert_eq ! ( count, 2 ) ;
3450+ // 1. Single reading
3451+ let mut readings_single: alloc:: vec:: Vec < ( i128 , u32 ) > = alloc:: vec:: Vec :: new ( ) ;
3452+ readings_single. push ( ( 100 , 5 ) ) ;
3453+ assert_eq ! ( OracleIntegrationManager :: calculate_weighted_median( & env, & readings_single, 5 ) , 100 ) ;
3454+
3455+ // 2. Even total weight, typical scenario
3456+ // weights: 10, 20, 30. Total weight = 60. Target = 30.
3457+ // prices: 100, 200, 300
3458+ // accumulated weights: 10 (at 100), 30 (at 200), 60 (at 300)
3459+ // target = 30. The first where accumulated > 30 is the last one (60).
3460+ let mut readings_even: alloc:: vec:: Vec < ( i128 , u32 ) > = alloc:: vec:: Vec :: new ( ) ;
3461+ readings_even. push ( ( 200 , 20 ) ) ;
3462+ readings_even. push ( ( 100 , 10 ) ) ;
3463+ readings_even. push ( ( 300 , 30 ) ) ;
3464+ assert_eq ! ( OracleIntegrationManager :: calculate_weighted_median( & env, & readings_even, 60 ) , 300 ) ;
3465+
3466+ // 3. Odd total weight
3467+ // weights: 10, 10, 10. Total = 30. Target = 15.
3468+ // prices: 100, 200, 300
3469+ // accumulated: 10, 20 (takes 200)
3470+ let mut readings_odd: alloc:: vec:: Vec < ( i128 , u32 ) > = alloc:: vec:: Vec :: new ( ) ;
3471+ readings_odd. push ( ( 300 , 10 ) ) ;
3472+ readings_odd. push ( ( 100 , 10 ) ) ;
3473+ readings_odd. push ( ( 200 , 10 ) ) ;
3474+ assert_eq ! ( OracleIntegrationManager :: calculate_weighted_median( & env, & readings_odd, 30 ) , 200 ) ;
3475+
3476+ // 4. Empty readings
3477+ let readings_empty: alloc:: vec:: Vec < ( i128 , u32 ) > = alloc:: vec:: Vec :: new ( ) ;
3478+ assert_eq ! ( OracleIntegrationManager :: calculate_weighted_median( & env, & readings_empty, 0 ) , 0 ) ;
34273479 }
34283480
34293481 #[ test]
0 commit comments