@@ -20,8 +20,21 @@ use oasis_runtime_sdk_rofl_market as market;
2020const ROFL_SCHEDULER_CONFIG_KEY : & str = "rofl_scheduler" ;
2121
2222/// Raw per-offer configuration as serialized.
23- #[ derive( Clone , Debug , Default , cbor:: Decode ) ]
23+ ///
24+ /// Each element of the `offers` sequence is either a plain string (old format, no overrides) or
25+ /// a map with an `id` field and optional per-offer config overrides:
26+ ///
27+ /// ```yaml
28+ /// offers:
29+ /// - public # plain string — global defaults apply
30+ /// - id: internal # map entry — per-offer overrides
31+ /// allowed_creators:
32+ /// - "oasis1..."
33+ /// ```
34+ #[ derive( Clone , Debug , Default ) ]
2435pub struct RawOfferConfig {
36+ /// Offer identifier (value of the `net.oasis.scheduler.offer` metadata key).
37+ pub id : String ,
2538 /// Allowed instance creator addresses for this offer.
2639 ///
2740 /// When set, overrides the global `allowed_creators`. An empty list means all creators are
@@ -34,60 +47,52 @@ pub struct RawOfferConfig {
3447 pub allowed_artifacts : Option < BTreeMap < String , Vec < String > > > ,
3548}
3649
37- /// A map entry with an explicit `id` field and optional per-offer config overrides.
38- #[ derive( Clone , Debug , Default , cbor:: Decode ) ]
39- struct RawOfferEntry {
40- pub id : String ,
41- pub allowed_creators : Option < Vec < String > > ,
42- pub allowed_artifacts : Option < BTreeMap < String , Vec < String > > > ,
43- }
44-
45- /// Backwards-compatible wrapper for the `offers` field in [`RawLocalConfig`].
46- ///
47- /// Each element of the sequence is either a plain string (old format, no overrides) or a
48- /// map with an `id` field and optional per-offer config overrides:
49- ///
50- /// ```yaml
51- /// offers:
52- /// - playground_short # plain string — global defaults apply
53- /// - id: oasis_internal # map entry — per-offer overrides
54- /// allowed_creators:
55- /// - "oasis1..."
56- /// ```
57- #[ derive( Clone , Debug , Default ) ]
58- pub ( crate ) struct RawOffersField ( BTreeMap < String , RawOfferConfig > ) ;
59-
60- impl cbor:: Decode for RawOffersField {
50+ impl cbor:: Decode for RawOfferConfig {
6151 fn try_default ( ) -> Result < Self , cbor:: DecodeError > {
62- Ok ( Self ( BTreeMap :: new ( ) ) )
52+ Ok ( Self :: default ( ) )
6353 }
6454
6555 fn try_from_cbor_value ( value : cbor:: Value ) -> Result < Self , cbor:: DecodeError > {
66- let cbor:: Value :: Array ( items) = value else {
67- return Err ( cbor:: DecodeError :: UnexpectedType ) ;
68- } ;
69- let mut map = BTreeMap :: new ( ) ;
70- for item in items {
71- match item {
72- // Plain string: "offer-name" → empty config (global defaults apply).
73- cbor:: Value :: TextString ( key) => {
74- map. insert ( key, RawOfferConfig :: default ( ) ) ;
56+ match value {
57+ // Plain string: the offer name with no overrides (global defaults apply).
58+ cbor:: Value :: TextString ( id) => Ok ( Self {
59+ id,
60+ allowed_creators : None ,
61+ allowed_artifacts : None ,
62+ } ) ,
63+ // Map with an `id` field and optional overrides.
64+ cbor:: Value :: Map ( entries) => {
65+ let mut id = None ;
66+ let mut allowed_creators = None ;
67+ let mut allowed_artifacts = None ;
68+ for ( k, v) in entries {
69+ let cbor:: Value :: TextString ( key) = k else {
70+ return Err ( cbor:: DecodeError :: UnexpectedType ) ;
71+ } ;
72+ match key. as_str ( ) {
73+ "id" => {
74+ id = Some ( <String as cbor:: Decode >:: try_from_cbor_value ( v) ?) ;
75+ }
76+ "allowed_creators" => {
77+ allowed_creators =
78+ Some ( <Vec < String > as cbor:: Decode >:: try_from_cbor_value ( v) ?) ;
79+ }
80+ "allowed_artifacts" => {
81+ allowed_artifacts = Some (
82+ <BTreeMap < String , Vec < String > > as cbor:: Decode >:: try_from_cbor_value ( v) ?,
83+ ) ;
84+ }
85+ _ => { }
86+ }
7587 }
76- // Map with an `id` field and optional overrides.
77- cbor:: Value :: Map ( _) => {
78- let entry = RawOfferEntry :: try_from_cbor_value ( item) ?;
79- map. insert (
80- entry. id ,
81- RawOfferConfig {
82- allowed_creators : entry. allowed_creators ,
83- allowed_artifacts : entry. allowed_artifacts ,
84- } ,
85- ) ;
86- }
87- _ => return Err ( cbor:: DecodeError :: UnexpectedType ) ,
88+ Ok ( Self {
89+ id : id. ok_or ( cbor:: DecodeError :: MissingField ) ?,
90+ allowed_creators,
91+ allowed_artifacts,
92+ } )
8893 }
94+ _ => Err ( cbor:: DecodeError :: UnexpectedType ) ,
8995 }
90- Ok ( Self ( map) )
9196 }
9297}
9398
@@ -98,11 +103,9 @@ pub struct RawLocalConfig {
98103 pub provider_address : String ,
99104 /// Offers that the scheduler should accept. If no offers are configured, all are accepted.
100105 ///
101- /// Each key is the value of the `net.oasis.scheduler.offer` metadata key. The value is an
102- /// optional per-offer configuration that overrides the global defaults.
103- ///
104- /// Accepts the legacy plain-string array form (backwards compatible) or the new map-entry form.
105- pub ( crate ) offers : RawOffersField ,
106+ /// Each entry is either a plain string (legacy form, no overrides) or a map with an `id`
107+ /// field and optional per-offer config overrides.
108+ pub offers : Vec < RawOfferConfig > ,
106109 /// Allowed artifact hashes.
107110 ///
108111 /// Key is the artifact kind and value is a list of artifact SHA256 hashes. If a key doesn't
@@ -291,9 +294,9 @@ impl LocalConfig {
291294
292295 let offers = cfg
293296 . offers
294- . 0
295297 . into_iter ( )
296- . map ( |( key, raw_cfg) | -> Result < ( String , OfferConfig ) > {
298+ . map ( |raw_cfg| -> Result < ( String , OfferConfig ) > {
299+ let key = raw_cfg. id ;
297300 let offer_creators = raw_cfg
298301 . allowed_creators
299302 . map ( |creators| {
@@ -376,7 +379,7 @@ impl LocalConfig {
376379 . unwrap_or ( & self . allowed_artifacts ) ;
377380
378381 match artifacts. get ( kind) {
379- None => Ok ( ( ) ) , // all artifacts of this kind are allowed
382+ None => Ok ( ( ) ) , // All artifacts of this kind are allowed.
380383 Some ( allowed_hashes) => {
381384 if !allowed_hashes. contains ( hash) {
382385 Err ( anyhow ! ( "{kind} artifact not allowed" ) )
@@ -408,38 +411,37 @@ impl LocalConfig {
408411
409412#[ cfg( test) ]
410413mod test {
414+ use oasis_runtime_sdk:: testing;
415+
411416 use super :: * ;
412417
413- fn make_raw ( offers_cbor : cbor:: Value ) -> RawLocalConfig {
414- let mut raw = RawLocalConfig :: default ( ) ;
415- raw. offers = cbor:: Decode :: try_from_cbor_value ( offers_cbor) . unwrap ( ) ;
416- raw
418+ fn decode_offers ( cbor : cbor:: Value ) -> Vec < RawOfferConfig > {
419+ <Vec < RawOfferConfig > as cbor:: Decode >:: try_from_cbor_value ( cbor) . unwrap ( )
417420 }
418421
419422 #[ test]
420423 fn test_offers_legacy_array_format ( ) {
421- // Old format: plain array of strings. Each becomes a key with empty OfferConfig .
422- let raw = make_raw ( cbor:: Value :: Array ( vec ! [
423- cbor:: Value :: TextString ( "playground_short " . into( ) ) ,
424- cbor:: Value :: TextString ( "playground_short_sgx " . into( ) ) ,
424+ // Old format: plain array of strings. Each becomes a RawOfferConfig with no overrides .
425+ let offers = decode_offers ( cbor:: Value :: Array ( vec ! [
426+ cbor:: Value :: TextString ( "small " . into( ) ) ,
427+ cbor:: Value :: TextString ( "large " . into( ) ) ,
425428 ] ) ) ;
426- assert ! ( raw. offers. 0 . contains_key( "playground_short" ) ) ;
427- assert ! ( raw. offers. 0 . contains_key( "playground_short_sgx" ) ) ;
428- assert_eq ! ( raw. offers. 0 . len( ) , 2 ) ;
429- // No per-offer overrides — both fields are None (global fallback applies).
430- assert ! ( raw. offers. 0 [ "playground_short" ] . allowed_creators. is_none( ) ) ;
429+ assert_eq ! ( offers. len( ) , 2 ) ;
430+ assert_eq ! ( offers[ 0 ] . id, "small" ) ;
431+ assert ! ( offers[ 0 ] . allowed_creators. is_none( ) ) ;
432+ assert_eq ! ( offers[ 1 ] . id, "large" ) ;
431433 }
432434
433435 #[ test]
434436 fn test_offers_mixed_format ( ) {
435437 // Mixed: plain strings alongside a map entry with an explicit `id` field and overrides.
436- let raw = make_raw ( cbor:: Value :: Array ( vec ! [
437- cbor:: Value :: TextString ( "playground_short " . into( ) ) ,
438- cbor:: Value :: TextString ( "playground_short_sgx " . into( ) ) ,
438+ let offers = decode_offers ( cbor:: Value :: Array ( vec ! [
439+ cbor:: Value :: TextString ( "small " . into( ) ) ,
440+ cbor:: Value :: TextString ( "large " . into( ) ) ,
439441 cbor:: Value :: Map ( vec![
440442 (
441443 cbor:: Value :: TextString ( "id" . into( ) ) ,
442- cbor:: Value :: TextString ( "oasis_internal " . into( ) ) ,
444+ cbor:: Value :: TextString ( "internal " . into( ) ) ,
443445 ) ,
444446 (
445447 cbor:: Value :: TextString ( "allowed_creators" . into( ) ) ,
@@ -449,10 +451,12 @@ mod test {
449451 ) ,
450452 ] ) ,
451453 ] ) ) ;
452- assert ! ( raw. offers. 0 . contains_key( "playground_short" ) ) ;
453- assert ! ( raw. offers. 0 [ "playground_short" ] . allowed_creators. is_none( ) ) ;
454- assert ! ( raw. offers. 0 . contains_key( "oasis_internal" ) ) ;
455- assert ! ( raw. offers. 0 [ "oasis_internal" ] . allowed_creators. is_some( ) ) ;
454+ assert_eq ! ( offers[ 0 ] . id, "small" ) ;
455+ assert ! ( offers[ 0 ] . allowed_creators. is_none( ) ) ;
456+ assert_eq ! ( offers[ 1 ] . id, "large" ) ;
457+ assert ! ( offers[ 1 ] . allowed_creators. is_none( ) ) ;
458+ assert_eq ! ( offers[ 2 ] . id, "internal" ) ;
459+ assert ! ( offers[ 2 ] . allowed_creators. is_some( ) ) ;
456460 }
457461
458462 #[ test]
@@ -463,16 +467,14 @@ mod test {
463467 allowed_creators : BTreeSet :: new ( ) , // global: allow all
464468 ..Default :: default ( )
465469 } ;
466- let addr = Address :: from_bech32 ( "oasis1qp0cnmkjl22gky6p7q0tgkwmsc6g4c5er6x0hsk7" ) . unwrap ( ) ;
470+ let addr = testing :: keys :: alice :: address ( ) ;
467471 assert ! ( cfg. is_creator_allowed( "public" , & addr) ) ;
468472 }
469473
470474 #[ test]
471475 fn test_is_creator_allowed_per_offer_override ( ) {
472- let allowed =
473- Address :: from_bech32 ( "oasis1qp0cnmkjl22gky6p7q0tgkwmsc6g4c5er6x0hsk7" ) . unwrap ( ) ;
474- let blocked =
475- Address :: from_bech32 ( "oasis1qrad7s7nqm4gvyzr8yt48jkrjxuqc6d7pvjm4ze" ) . unwrap ( ) ;
476+ let allowed = testing:: keys:: alice:: address ( ) ;
477+ let blocked = testing:: keys:: bob:: address ( ) ;
476478
477479 let cfg = LocalConfig {
478480 offers : BTreeMap :: from ( [ (
0 commit comments