Skip to main content

ave_core/evaluation/
request.rs

1use crate::{
2    evaluation::{response::EvaluatorError, runner::types::EvaluateInfo},
3    governance::data::GovernanceData,
4    model::common::viewpoints::validate_fact_viewpoints,
5};
6use ave_common::{
7    Namespace, SchemaType, ValueWrapper,
8    identity::{DigestIdentifier, PublicKey, Signed},
9    request::EventRequest,
10};
11
12use borsh::{BorshDeserialize, BorshSerialize};
13use serde::{Deserialize, Serialize};
14use std::collections::{BTreeMap, BTreeSet};
15
16#[derive(Debug, Clone, Default)]
17pub enum EvalWorkerContext {
18    #[default]
19    Empty,
20    Governance {
21        issuers: BTreeSet<PublicKey>,
22        issuer_any: bool,
23    },
24    TrackerFact {
25        issuers: BTreeSet<PublicKey>,
26        issuer_any: bool,
27        schema_viewpoints: BTreeSet<String>,
28    },
29    TrackerTransfer {
30        members: BTreeSet<PublicKey>,
31        creators: BTreeMap<PublicKey, BTreeSet<Namespace>>,
32    },
33}
34
35impl EvalWorkerContext {
36    pub const fn issuers(&self) -> Option<(&BTreeSet<PublicKey>, bool)> {
37        match self {
38            Self::Governance {
39                issuers,
40                issuer_any,
41            }
42            | Self::TrackerFact {
43                issuers,
44                issuer_any,
45                ..
46            } => Some((issuers, *issuer_any)),
47            Self::Empty | Self::TrackerTransfer { .. } => None,
48        }
49    }
50}
51
52/// A struct representing an evaluation request.
53#[derive(
54    Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
55)]
56pub struct EvaluationReq {
57    /// The signed event request.
58    pub event_request: Signed<EventRequest>,
59
60    pub governance_id: DigestIdentifier,
61
62    pub data: EvaluateData,
63
64    pub sn: u64,
65
66    pub gov_version: u64,
67
68    pub namespace: Namespace,
69
70    pub schema_id: SchemaType,
71
72    pub signer: PublicKey,
73
74    pub signer_is_owner: bool,
75}
76
77impl EvaluationReq {
78    fn validate_fact_viewpoints(
79        fact_viewpoints: &BTreeSet<String>,
80        schema_id: &SchemaType,
81        schema_viewpoints: Option<&BTreeSet<String>>,
82    ) -> Result<(), EvaluatorError> {
83        validate_fact_viewpoints(fact_viewpoints, schema_id, schema_viewpoints)
84            .map_err(EvaluatorError::InvalidEventRequest)
85    }
86
87    pub fn build_evaluate_info(
88        &self,
89        init_state: &Option<ValueWrapper>,
90        worker_context: &EvalWorkerContext,
91    ) -> Result<EvaluateInfo, EvaluatorError> {
92        match (self.event_request.content(), &self.data) {
93            (
94                EventRequest::Fact(fact_request),
95                EvaluateData::GovFact { state },
96            ) => {
97                Self::validate_fact_viewpoints(
98                    &fact_request.viewpoints,
99                    &self.schema_id,
100                    None,
101                )?;
102
103                Ok(EvaluateInfo::GovFact {
104                    payload: fact_request.payload.clone(),
105                    state: state.clone(),
106                })
107            }
108            (
109                EventRequest::Fact(fact_request),
110                EvaluateData::TrackerSchemasFact { state },
111            ) => init_state.as_ref().map_or_else(
112                || {
113                    Err(EvaluatorError::InternalError(
114                        "Init state must be some".to_owned(),
115                    ))
116                },
117                |init_state| {
118                    let EvalWorkerContext::TrackerFact {
119                        schema_viewpoints,
120                        ..
121                    } = worker_context
122                    else {
123                        return Err(EvaluatorError::InternalError(
124                            "Tracker fact evaluation context is missing"
125                                .to_owned(),
126                        ));
127                    };
128
129                    Self::validate_fact_viewpoints(
130                        &fact_request.viewpoints,
131                        &self.schema_id,
132                        Some(schema_viewpoints),
133                    )?;
134
135                    Ok(EvaluateInfo::TrackerSchemasFact {
136                        contract: format!(
137                            "{}_{}",
138                            self.governance_id, self.schema_id
139                        ),
140                        init_state: init_state.clone(),
141                        state: state.clone(),
142                        payload: fact_request.payload.clone(),
143                    })
144                },
145            ),
146            (
147                EventRequest::Transfer(transfer_request),
148                EvaluateData::GovTransfer { state },
149            ) => Ok(EvaluateInfo::GovTransfer {
150                new_owner: transfer_request.new_owner.clone(),
151                state: state.clone(),
152            }),
153            (
154                EventRequest::Transfer(transfer_request),
155                EvaluateData::TrackerSchemasTransfer { .. },
156            ) => {
157                let EvalWorkerContext::TrackerTransfer { members, creators } =
158                    worker_context
159                else {
160                    return Err(EvaluatorError::InternalError(
161                        "Tracker transfer evaluation context is missing"
162                            .to_owned(),
163                    ));
164                };
165
166                Ok(EvaluateInfo::TrackerSchemasTransfer {
167                    new_owner: transfer_request.new_owner.clone(),
168                    old_owner: self.event_request.signature().signer.clone(),
169                    namespace: self.namespace.clone(),
170                    schema_id: self.schema_id.clone(),
171                    members: members.clone(),
172                    creators: creators.clone(),
173                })
174            }
175            (
176                EventRequest::Confirm(confirm_request),
177                EvaluateData::GovConfirm { state },
178            ) => Ok(EvaluateInfo::GovConfirm {
179                new_owner: self.event_request.signature().signer.clone(),
180                old_owner_name: confirm_request.name_old_owner.clone(),
181                state: state.clone(),
182            }),
183            _ => Err(EvaluatorError::InvalidEventRequest(
184                "Evaluate data does not correspond to the type of request"
185                    .to_string(),
186            )),
187        }
188    }
189}
190
191#[derive(
192    Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
193)]
194pub enum EvaluateData {
195    GovFact { state: GovernanceData },
196    GovTransfer { state: GovernanceData },
197    GovConfirm { state: GovernanceData },
198    TrackerSchemasFact { state: ValueWrapper },
199    TrackerSchemasTransfer { state: ValueWrapper },
200}
201
202impl EvaluateData {
203    pub const fn is_gov_event(&self) -> bool {
204        match self {
205            Self::GovFact { .. }
206            | Self::GovTransfer { .. }
207            | Self::GovConfirm { .. } => true,
208            Self::TrackerSchemasFact { .. }
209            | Self::TrackerSchemasTransfer { .. } => false,
210        }
211    }
212}
213
214/// A struct representing the context in which the evaluation is being performed.
215#[derive(
216    Debug,
217    Clone,
218    Serialize,
219    Deserialize,
220    Eq,
221    PartialEq,
222    BorshSerialize,
223    BorshDeserialize,
224)]
225pub struct SubjectContext {
226    pub subject_id: DigestIdentifier,
227    pub governance_id: DigestIdentifier,
228    pub schema_id: SchemaType,
229    pub is_owner: bool,
230    pub namespace: Namespace,
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236    use ave_common::identity::{
237        Signed,
238        keys::{Ed25519Signer, KeyPair},
239    };
240    use serde_json::json;
241
242    #[test]
243    fn test_build_evaluate_info_rejects_governance_fact_viewpoints() {
244        let signer = Ed25519Signer::generate().unwrap();
245        let public_key = KeyPair::Ed25519(signer.clone()).public_key();
246        let request = EventRequest::Fact(ave_common::request::FactRequest {
247            subject_id: DigestIdentifier::default(),
248            payload: ValueWrapper(json!({ "members": { "add": [] } })),
249            viewpoints: BTreeSet::from(["agua".to_owned()]),
250        });
251
252        let event_request = Signed::new(request, &signer).unwrap();
253
254        let req = EvaluationReq {
255            event_request,
256            governance_id: DigestIdentifier::default(),
257            data: EvaluateData::GovFact {
258                state: GovernanceData::new(public_key.clone()),
259            },
260            sn: 1,
261            gov_version: 0,
262            namespace: Namespace::default(),
263            schema_id: SchemaType::Governance,
264            signer: public_key,
265            signer_is_owner: true,
266        };
267
268        let error = req
269            .build_evaluate_info(&None, &EvalWorkerContext::default())
270            .unwrap_err();
271        assert!(matches!(error, EvaluatorError::InvalidEventRequest(_)));
272    }
273
274    #[test]
275    fn test_build_evaluate_info_rejects_unknown_tracker_fact_viewpoint() {
276        let signer = Ed25519Signer::generate().unwrap();
277        let public_key = KeyPair::Ed25519(signer.clone()).public_key();
278        let request = EventRequest::Fact(ave_common::request::FactRequest {
279            subject_id: DigestIdentifier::default(),
280            payload: ValueWrapper(json!({ "ModOne": { "data": 1 } })),
281            viewpoints: BTreeSet::from(["vidrio".to_owned()]),
282        });
283
284        let event_request = Signed::new(request, &signer).unwrap();
285
286        let req = EvaluationReq {
287            event_request,
288            governance_id: DigestIdentifier::default(),
289            data: EvaluateData::TrackerSchemasFact {
290                state: ValueWrapper(json!({ "one": 0, "two": 0, "three": 0 })),
291            },
292            sn: 1,
293            gov_version: 0,
294            namespace: Namespace::default(),
295            schema_id: SchemaType::Type("Example".to_owned()),
296            signer: public_key,
297            signer_is_owner: true,
298        };
299
300        let error = req
301            .build_evaluate_info(
302                &Some(ValueWrapper(json!({}))),
303                &EvalWorkerContext::TrackerFact {
304                    issuers: BTreeSet::new(),
305                    issuer_any: false,
306                    schema_viewpoints: BTreeSet::from([
307                        "agua".to_owned(),
308                        "basura".to_owned(),
309                    ]),
310                },
311            )
312            .unwrap_err();
313        assert!(matches!(error, EvaluatorError::InvalidEventRequest(_)));
314    }
315
316    #[test]
317    fn test_build_evaluate_info_rejects_all_viewpoints_in_tracker_fact() {
318        let signer = Ed25519Signer::generate().unwrap();
319        let public_key = KeyPair::Ed25519(signer.clone()).public_key();
320        let request = EventRequest::Fact(ave_common::request::FactRequest {
321            subject_id: DigestIdentifier::default(),
322            payload: ValueWrapper(json!({ "ModOne": { "data": 1 } })),
323            viewpoints: BTreeSet::from(["AllViewpoints".to_owned()]),
324        });
325
326        let event_request = Signed::new(request, &signer).unwrap();
327
328        let req = EvaluationReq {
329            event_request,
330            governance_id: DigestIdentifier::default(),
331            data: EvaluateData::TrackerSchemasFact {
332                state: ValueWrapper(json!({ "one": 0, "two": 0, "three": 0 })),
333            },
334            sn: 1,
335            gov_version: 0,
336            namespace: Namespace::default(),
337            schema_id: SchemaType::Type("Example".to_owned()),
338            signer: public_key,
339            signer_is_owner: true,
340        };
341
342        let error = req
343            .build_evaluate_info(
344                &Some(ValueWrapper(json!({}))),
345                &EvalWorkerContext::TrackerFact {
346                    issuers: BTreeSet::new(),
347                    issuer_any: false,
348                    schema_viewpoints: BTreeSet::from([
349                        "agua".to_owned(),
350                        "basura".to_owned(),
351                    ]),
352                },
353            )
354            .unwrap_err();
355        assert!(matches!(error, EvaluatorError::InvalidEventRequest(_)));
356    }
357
358    #[test]
359    fn test_build_evaluate_info_rejects_unknown_no_viewpoints_viewpoint() {
360        let signer = Ed25519Signer::generate().unwrap();
361        let public_key = KeyPair::Ed25519(signer.clone()).public_key();
362        let request = EventRequest::Fact(ave_common::request::FactRequest {
363            subject_id: DigestIdentifier::default(),
364            payload: ValueWrapper(json!({ "ModOne": { "data": 1 } })),
365            viewpoints: BTreeSet::from(["NoViewpoints".to_owned()]),
366        });
367
368        let event_request = Signed::new(request, &signer).unwrap();
369
370        let req = EvaluationReq {
371            event_request,
372            governance_id: DigestIdentifier::default(),
373            data: EvaluateData::TrackerSchemasFact {
374                state: ValueWrapper(json!({ "one": 0, "two": 0, "three": 0 })),
375            },
376            sn: 1,
377            gov_version: 0,
378            namespace: Namespace::default(),
379            schema_id: SchemaType::Type("Example".to_owned()),
380            signer: public_key,
381            signer_is_owner: true,
382        };
383
384        let error = req
385            .build_evaluate_info(
386                &Some(ValueWrapper(json!({}))),
387                &EvalWorkerContext::TrackerFact {
388                    issuers: BTreeSet::new(),
389                    issuer_any: false,
390                    schema_viewpoints: BTreeSet::from([
391                        "agua".to_owned(),
392                        "basura".to_owned(),
393                    ]),
394                },
395            )
396            .unwrap_err();
397        assert!(matches!(error, EvaluatorError::InvalidEventRequest(_)));
398    }
399}