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 {
111                    state,
112                },
113            ) => init_state.as_ref().map_or_else(
114                || {
115                    Err(EvaluatorError::InternalError(
116                        "Init state must be some".to_owned(),
117                    ))
118                },
119                |init_state| {
120                    let EvalWorkerContext::TrackerFact {
121                        schema_viewpoints,
122                        ..
123                    } = worker_context
124                    else {
125                        return Err(EvaluatorError::InternalError(
126                            "Tracker fact evaluation context is missing"
127                                .to_owned(),
128                        ));
129                    };
130
131                    Self::validate_fact_viewpoints(
132                        &fact_request.viewpoints,
133                        &self.schema_id,
134                        Some(schema_viewpoints),
135                    )?;
136
137                    Ok(EvaluateInfo::TrackerSchemasFact {
138                        contract: format!(
139                            "{}_{}",
140                            self.governance_id, self.schema_id
141                        ),
142                        init_state: init_state.clone(),
143                        state: state.clone(),
144                        payload: fact_request.payload.clone(),
145                    })
146                },
147            ),
148            (
149                EventRequest::Transfer(transfer_request),
150                EvaluateData::GovTransfer { state },
151            ) => Ok(EvaluateInfo::GovTransfer {
152                new_owner: transfer_request.new_owner.clone(),
153                state: state.clone(),
154            }),
155            (
156                EventRequest::Transfer(transfer_request),
157                EvaluateData::TrackerSchemasTransfer { .. },
158            ) => {
159                let EvalWorkerContext::TrackerTransfer {
160                    members,
161                    creators,
162                } = worker_context
163                else {
164                    return Err(EvaluatorError::InternalError(
165                        "Tracker transfer evaluation context is missing"
166                            .to_owned(),
167                    ));
168                };
169
170                Ok(EvaluateInfo::TrackerSchemasTransfer {
171                    new_owner: transfer_request.new_owner.clone(),
172                    old_owner: self.event_request.signature().signer.clone(),
173                    namespace: self.namespace.clone(),
174                    schema_id: self.schema_id.clone(),
175                    members: members.clone(),
176                    creators: creators.clone(),
177                })
178            }
179            (
180                EventRequest::Confirm(confirm_request),
181                EvaluateData::GovConfirm { state },
182            ) => Ok(EvaluateInfo::GovConfirm {
183                new_owner: self.event_request.signature().signer.clone(),
184                old_owner_name: confirm_request.name_old_owner.clone(),
185                state: state.clone(),
186            }),
187            _ => Err(EvaluatorError::InvalidEventRequest(
188                "Evaluate data does not correspond to the type of request"
189                    .to_string(),
190            )),
191        }
192    }
193}
194
195#[derive(
196    Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
197)]
198pub enum EvaluateData {
199    GovFact {
200        state: GovernanceData,
201    },
202    GovTransfer {
203        state: GovernanceData,
204    },
205    GovConfirm {
206        state: GovernanceData,
207    },
208    TrackerSchemasFact {
209        state: ValueWrapper,
210    },
211    TrackerSchemasTransfer {
212        state: ValueWrapper,
213    },
214}
215
216impl EvaluateData {
217    pub const fn is_gov_event(&self) -> bool {
218        match self {
219            Self::GovFact { .. }
220            | Self::GovTransfer { .. }
221            | Self::GovConfirm { .. } => true,
222            Self::TrackerSchemasFact { .. }
223            | Self::TrackerSchemasTransfer { .. } => false,
224        }
225    }
226}
227
228/// A struct representing the context in which the evaluation is being performed.
229#[derive(
230    Debug,
231    Clone,
232    Serialize,
233    Deserialize,
234    Eq,
235    PartialEq,
236    BorshSerialize,
237    BorshDeserialize,
238)]
239pub struct SubjectContext {
240    pub subject_id: DigestIdentifier,
241    pub governance_id: DigestIdentifier,
242    pub schema_id: SchemaType,
243    pub is_owner: bool,
244    pub namespace: Namespace,
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250    use ave_common::identity::{
251        Signed,
252        keys::{Ed25519Signer, KeyPair},
253    };
254    use serde_json::json;
255
256    #[test]
257    fn test_build_evaluate_info_rejects_governance_fact_viewpoints() {
258        let signer = Ed25519Signer::generate().unwrap();
259        let public_key = KeyPair::Ed25519(signer.clone()).public_key();
260        let request = EventRequest::Fact(ave_common::request::FactRequest {
261            subject_id: DigestIdentifier::default(),
262            payload: ValueWrapper(json!({ "members": { "add": [] } })),
263            viewpoints: BTreeSet::from(["agua".to_owned()]),
264        });
265
266        let event_request = Signed::new(request, &signer).unwrap();
267
268        let req = EvaluationReq {
269            event_request,
270            governance_id: DigestIdentifier::default(),
271            data: EvaluateData::GovFact {
272                state: GovernanceData::new(public_key.clone()),
273            },
274            sn: 1,
275            gov_version: 0,
276            namespace: Namespace::default(),
277            schema_id: SchemaType::Governance,
278            signer: public_key,
279            signer_is_owner: true,
280        };
281
282        let error = req
283            .build_evaluate_info(&None, &EvalWorkerContext::default())
284            .unwrap_err();
285        assert!(matches!(error, EvaluatorError::InvalidEventRequest(_)));
286    }
287
288    #[test]
289    fn test_build_evaluate_info_rejects_unknown_tracker_fact_viewpoint() {
290        let signer = Ed25519Signer::generate().unwrap();
291        let public_key = KeyPair::Ed25519(signer.clone()).public_key();
292        let request = EventRequest::Fact(ave_common::request::FactRequest {
293            subject_id: DigestIdentifier::default(),
294            payload: ValueWrapper(json!({ "ModOne": { "data": 1 } })),
295            viewpoints: BTreeSet::from(["vidrio".to_owned()]),
296        });
297
298        let event_request = Signed::new(request, &signer).unwrap();
299
300        let req = EvaluationReq {
301            event_request,
302            governance_id: DigestIdentifier::default(),
303            data: EvaluateData::TrackerSchemasFact {
304                state: ValueWrapper(json!({ "one": 0, "two": 0, "three": 0 })),
305            },
306            sn: 1,
307            gov_version: 0,
308            namespace: Namespace::default(),
309            schema_id: SchemaType::Type("Example".to_owned()),
310            signer: public_key,
311            signer_is_owner: true,
312        };
313
314        let error = req
315            .build_evaluate_info(
316                &Some(ValueWrapper(json!({}))),
317                &EvalWorkerContext::TrackerFact {
318                    issuers: BTreeSet::new(),
319                    issuer_any: false,
320                    schema_viewpoints: BTreeSet::from([
321                        "agua".to_owned(),
322                        "basura".to_owned(),
323                    ]),
324                },
325            )
326            .unwrap_err();
327        assert!(matches!(error, EvaluatorError::InvalidEventRequest(_)));
328    }
329
330    #[test]
331    fn test_build_evaluate_info_rejects_all_viewpoints_in_tracker_fact() {
332        let signer = Ed25519Signer::generate().unwrap();
333        let public_key = KeyPair::Ed25519(signer.clone()).public_key();
334        let request = EventRequest::Fact(ave_common::request::FactRequest {
335            subject_id: DigestIdentifier::default(),
336            payload: ValueWrapper(json!({ "ModOne": { "data": 1 } })),
337            viewpoints: BTreeSet::from(["AllViewpoints".to_owned()]),
338        });
339
340        let event_request = Signed::new(request, &signer).unwrap();
341
342        let req = EvaluationReq {
343            event_request,
344            governance_id: DigestIdentifier::default(),
345            data: EvaluateData::TrackerSchemasFact {
346                state: ValueWrapper(json!({ "one": 0, "two": 0, "three": 0 })),
347            },
348            sn: 1,
349            gov_version: 0,
350            namespace: Namespace::default(),
351            schema_id: SchemaType::Type("Example".to_owned()),
352            signer: public_key,
353            signer_is_owner: true,
354        };
355
356        let error = req
357            .build_evaluate_info(
358                &Some(ValueWrapper(json!({}))),
359                &EvalWorkerContext::TrackerFact {
360                    issuers: BTreeSet::new(),
361                    issuer_any: false,
362                    schema_viewpoints: BTreeSet::from([
363                        "agua".to_owned(),
364                        "basura".to_owned(),
365                    ]),
366                },
367            )
368            .unwrap_err();
369        assert!(matches!(error, EvaluatorError::InvalidEventRequest(_)));
370    }
371
372    #[test]
373    fn test_build_evaluate_info_rejects_unknown_no_viewpoints_viewpoint() {
374        let signer = Ed25519Signer::generate().unwrap();
375        let public_key = KeyPair::Ed25519(signer.clone()).public_key();
376        let request = EventRequest::Fact(ave_common::request::FactRequest {
377            subject_id: DigestIdentifier::default(),
378            payload: ValueWrapper(json!({ "ModOne": { "data": 1 } })),
379            viewpoints: BTreeSet::from(["NoViewpoints".to_owned()]),
380        });
381
382        let event_request = Signed::new(request, &signer).unwrap();
383
384        let req = EvaluationReq {
385            event_request,
386            governance_id: DigestIdentifier::default(),
387            data: EvaluateData::TrackerSchemasFact {
388                state: ValueWrapper(json!({ "one": 0, "two": 0, "three": 0 })),
389            },
390            sn: 1,
391            gov_version: 0,
392            namespace: Namespace::default(),
393            schema_id: SchemaType::Type("Example".to_owned()),
394            signer: public_key,
395            signer_is_owner: true,
396        };
397
398        let error = req
399            .build_evaluate_info(
400                &Some(ValueWrapper(json!({}))),
401                &EvalWorkerContext::TrackerFact {
402                    issuers: BTreeSet::new(),
403                    issuer_any: false,
404                    schema_viewpoints: BTreeSet::from([
405                        "agua".to_owned(),
406                        "basura".to_owned(),
407                    ]),
408                },
409            )
410            .unwrap_err();
411        assert!(matches!(error, EvaluatorError::InvalidEventRequest(_)));
412    }
413}