dusk_node_data/events/
contract.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4//
5// Copyright (c) DUSK NETWORK. All rights reserved.
6
7//! This module defines the contract event type and related types.
8
9use anyhow::Result;
10use dusk_core::abi::{ContractId, Event, CONTRACT_ID_BYTES};
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12
13pub const ORIGIN_HASH_BYTES: usize = 32;
14/// Origin hash of a contract event. This is in most cases the transaction hash.
15/// In the case of a reward or slash event, it is the block hash.
16pub type OriginHash = [u8; ORIGIN_HASH_BYTES];
17
18/// Contract event with origin `OriginHash`.
19#[serde_with::serde_as]
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
21pub struct ContractTxEvent {
22    pub event: ContractEvent,
23    #[serde_as(as = "serde_with::hex::Hex")]
24    pub origin: OriginHash,
25}
26
27#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
28#[repr(C)]
29pub struct WrappedContractId(pub ContractId);
30
31impl TryFrom<String> for WrappedContractId {
32    type Error = anyhow::Error;
33
34    fn try_from(value: String) -> std::result::Result<Self, Self::Error> {
35        let source_bytes = hex::decode(value)?;
36        let mut source_array = [0u8; CONTRACT_ID_BYTES];
37
38        if source_bytes.len() != CONTRACT_ID_BYTES {
39            return Err(anyhow::anyhow!(
40                "Invalid length: expected {} bytes, got {}",
41                CONTRACT_ID_BYTES,
42                source_bytes.len()
43            ));
44        } else {
45            source_array.copy_from_slice(&source_bytes);
46        }
47
48        Ok(WrappedContractId(ContractId::from_bytes(source_array)))
49    }
50}
51
52impl Serialize for WrappedContractId {
53    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
54    where
55        S: Serializer,
56    {
57        let source_hex = hex::encode(self.0.as_bytes());
58        s.serialize_str(&source_hex)
59    }
60}
61
62impl<'de> Deserialize<'de> for WrappedContractId {
63    fn deserialize<D>(deserializer: D) -> Result<WrappedContractId, D::Error>
64    where
65        D: Deserializer<'de>,
66    {
67        let source_hex: String = Deserialize::deserialize(deserializer)?;
68        let source_bytes =
69            hex::decode(source_hex).map_err(serde::de::Error::custom)?;
70        let mut source_array = [0u8; CONTRACT_ID_BYTES];
71
72        if source_bytes.len() != CONTRACT_ID_BYTES {
73            return Err(serde::de::Error::custom(format!(
74                "Invalid length: expected {} bytes, got {}",
75                CONTRACT_ID_BYTES,
76                source_bytes.len()
77            )));
78        } else {
79            source_array.copy_from_slice(&source_bytes);
80        }
81
82        Ok(WrappedContractId(ContractId::from_bytes(source_array)))
83    }
84}
85
86/// Wrapper around a contract event that is to be archived or sent to a
87/// websocket client.
88#[serde_with::serde_as]
89#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
90pub struct ContractEvent {
91    pub target: WrappedContractId,
92    pub topic: String,
93    #[serde_as(as = "serde_with::hex::Hex")]
94    pub data: Vec<u8>,
95}
96
97impl From<Event> for ContractEvent {
98    fn from(event: Event) -> Self {
99        Self {
100            target: WrappedContractId(event.source),
101            topic: event.topic,
102            data: event.data,
103        }
104    }
105}
106
107impl From<ContractEvent> for Event {
108    fn from(contract_event: ContractEvent) -> Self {
109        Event {
110            source: contract_event.target.0,
111            topic: contract_event.topic,
112            data: contract_event.data,
113        }
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    fn exec_core_event() -> Event {
122        Event {
123            source: ContractId::from_bytes([0; CONTRACT_ID_BYTES]),
124            topic: "contract".to_string(),
125            data: vec![1, 2, 3],
126        }
127    }
128
129    #[test]
130    fn test_converting_contract_event() {
131        let contract_event: ContractEvent = exec_core_event().into();
132
133        assert_eq!(Event::from(contract_event), exec_core_event());
134    }
135
136    #[test]
137    fn test_serialize_contract_event() {
138        let event: ContractEvent = exec_core_event().into();
139        let json_event = serde_json::to_string(&event).unwrap();
140        assert_eq!(event, serde_json::from_str(&json_event).unwrap());
141
142        let events: Vec<ContractEvent> = vec![event.clone(), event];
143        let json_events = serde_json::to_string(&events).unwrap();
144        assert_eq!(
145            events,
146            serde_json::from_str::<Vec<ContractEvent>>(&json_events).unwrap()
147        );
148
149        let empty_events: Vec<ContractEvent> = vec![];
150        let empty_json_events = serde_json::to_string(&empty_events).unwrap();
151        assert_eq!(
152            empty_events,
153            serde_json::from_str::<Vec<ContractEvent>>(&empty_json_events)
154                .unwrap()
155        );
156    }
157}