Skip to main content

dpp/group/group_action/
mod.rs

1pub mod v0;
2
3use crate::data_contract::TokenContractPosition;
4use crate::group::action_event::GroupActionEvent;
5use crate::group::group_action::v0::GroupActionV0;
6#[cfg(feature = "json-conversion")]
7use crate::serialization::JsonConvertible;
8#[cfg(feature = "value-conversion")]
9use crate::serialization::ValueConvertible;
10use crate::ProtocolError;
11use bincode::{Decode, DecodeUntrusted, Encode};
12use platform_serialization_derive::{
13    PlatformDeserializeTrusted, PlatformDeserializeUntrusted, PlatformSerialize,
14};
15use platform_value::Identifier;
16#[cfg(feature = "serde-conversion")]
17use serde::{Deserialize, Serialize};
18
19#[cfg_attr(
20    all(feature = "json-conversion", feature = "serde-conversion"),
21    derive(JsonConvertible)
22)]
23#[derive(
24    Debug,
25    PartialEq,
26    PartialOrd,
27    Clone,
28    Eq,
29    Encode,
30    Decode,
31    PlatformDeserializeTrusted,
32    PlatformDeserializeUntrusted,
33    PlatformSerialize,
34    DecodeUntrusted,
35)]
36#[cfg_attr(
37    feature = "serde-conversion",
38    derive(Serialize, Deserialize),
39    serde(tag = "$formatVersion")
40)]
41#[cfg_attr(feature = "value-conversion", derive(ValueConvertible))]
42// Stored group actions are decoded from GroveDB proof elements on the client
43// before the quorum signature is checked, so the byte budget must be enforced
44// by the decoder itself. Every payload (notes, config change, pricing
45// schedule) is copied out of the state transition that proposed the action,
46// and `StateTransition` is capped at the same 100,000 bytes, so no valid
47// stored action can exceed this.
48#[platform_serialize(limit = 100000, unversioned)] //versioned directly, no need to use platform_version
49pub enum GroupAction {
50    #[cfg_attr(feature = "serde-conversion", serde(rename = "0"))]
51    V0(GroupActionV0),
52}
53
54pub trait GroupActionAccessors {
55    fn contract_id(&self) -> Identifier;
56
57    fn proposer_id(&self) -> Identifier;
58    fn token_contract_position(&self) -> TokenContractPosition;
59    fn event(&self) -> &GroupActionEvent;
60}
61impl GroupActionAccessors for GroupAction {
62    fn contract_id(&self) -> Identifier {
63        match self {
64            GroupAction::V0(inner) => inner.contract_id(),
65        }
66    }
67
68    fn proposer_id(&self) -> Identifier {
69        match self {
70            GroupAction::V0(inner) => inner.proposer_id(),
71        }
72    }
73
74    fn token_contract_position(&self) -> TokenContractPosition {
75        match self {
76            GroupAction::V0(inner) => inner.token_contract_position(),
77        }
78    }
79
80    fn event(&self) -> &GroupActionEvent {
81        match self {
82            GroupAction::V0(inner) => inner.event(),
83        }
84    }
85}
86
87// TODO(unification pass 2): add round-trip tests for GroupAction once we have an
88// explicit fixture (GroupActionV0 has no Default — its `event: GroupActionEvent`
89// field is itself a versioned enum without Default).
90
91#[cfg(test)]
92mod deserialize_limit_tests {
93    use super::*;
94    use crate::serialization::PlatformDeserializableUntrusted;
95
96    /// A proof element is untrusted input: a note length prefix must be
97    /// rejected against the byte budget before it sizes an allocation.
98    #[test]
99    fn rejects_note_length_prefix_beyond_budget_without_allocating() {
100        let config = bincode::config::standard()
101            .with_big_endian()
102            .with_no_limit();
103        let mut buf = Vec::new();
104        // GroupAction::V0, then GroupActionV0 { contract_id, proposer_id, position, event }
105        buf.extend_from_slice(&bincode::encode_to_vec(0u32, config).unwrap());
106        buf.extend_from_slice(&[0u8; 32]);
107        buf.extend_from_slice(&[0u8; 32]);
108        buf.extend_from_slice(&bincode::encode_to_vec(0u16, config).unwrap());
109        // GroupActionEvent::TokenEvent(TokenEvent::Freeze(id, Some(note)))
110        buf.extend_from_slice(&bincode::encode_to_vec(0u32, config).unwrap());
111        buf.extend_from_slice(&bincode::encode_to_vec(2u32, config).unwrap());
112        buf.extend_from_slice(&[0u8; 32]);
113        buf.push(1);
114        // note length prefix claiming 8 GB, with no bytes following it
115        buf.extend_from_slice(&bincode::encode_to_vec(8_000_000_000u64, config).unwrap());
116
117        let err = GroupAction::deserialize_from_bytes_untrusted(&buf)
118            .expect_err("oversized length prefix must be rejected");
119        assert!(
120            matches!(err, ProtocolError::MaxEncodedBytesReachedError { .. }),
121            "unexpected error: {err}"
122        );
123    }
124}