nifty_asset_types/extensions/
grouping.rs

1use bytemuck::{Pod, Zeroable};
2use podded::pod::{Nullable, PodOption};
3use solana_program::pubkey::Pubkey;
4use std::{
5    fmt::Debug,
6    ops::{Deref, DerefMut},
7};
8
9use crate::{error::Error, state::NullablePubkey};
10
11use super::{ExtensionBuilder, ExtensionData, ExtensionDataMut, ExtensionType, Lifecycle};
12
13/// Extension to define a group of assets.
14///
15/// Assets that are intented to be use as group "markers" must have this extension
16/// attached to them.
17///
18/// The `size` of the group is updated every time an asset is added or removed from the group.
19/// Additionally, the `size` is decreased when an asset is burned.
20pub struct Grouping<'a> {
21    /// The number of assets in the group.
22    pub size: &'a u64,
23
24    /// The maximum number of assets that can be in the group.
25    ///
26    /// When the group is unlimited, this value is `0`.
27    pub max_size: &'a PodOption<NullableU64>,
28
29    /// An optional delegate authorised to add assets to this group
30    pub delegate: &'a PodOption<NullablePubkey>,
31}
32
33impl<'a> ExtensionData<'a> for Grouping<'a> {
34    const TYPE: ExtensionType = ExtensionType::Grouping;
35
36    fn from_bytes(bytes: &'a [u8]) -> Self {
37        let (size, rest) = bytes.split_at(std::mem::size_of::<u64>());
38        let (max_size, delegate) = rest.split_at(std::mem::size_of::<u64>());
39        Self {
40            size: bytemuck::from_bytes(size),
41            max_size: bytemuck::from_bytes(max_size),
42            delegate: bytemuck::from_bytes(delegate),
43        }
44    }
45
46    fn length(&self) -> usize {
47        std::mem::size_of::<u64>() + std::mem::size_of::<u64>() + std::mem::size_of::<Pubkey>()
48    }
49}
50
51impl Debug for Grouping<'_> {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        f.debug_struct("Group")
54            .field("size", &self.size)
55            .field("max_size", &self.max_size.value())
56            .field("delegate", &self.delegate.value())
57            .finish()
58    }
59}
60
61pub struct GroupingMut<'a> {
62    pub size: &'a mut u64,
63
64    pub max_size: &'a mut PodOption<NullableU64>,
65
66    pub delegate: &'a mut PodOption<NullablePubkey>,
67}
68
69impl<'a> ExtensionDataMut<'a> for GroupingMut<'a> {
70    const TYPE: ExtensionType = ExtensionType::Grouping;
71
72    fn from_bytes_mut(bytes: &'a mut [u8]) -> Self {
73        let (size, rest) = bytes.split_at_mut(std::mem::size_of::<u64>());
74        let (max_size, delegate) = rest.split_at_mut(std::mem::size_of::<u64>());
75        Self {
76            size: bytemuck::from_bytes_mut(size),
77            max_size: bytemuck::from_bytes_mut(max_size),
78            delegate: bytemuck::from_bytes_mut(delegate),
79        }
80    }
81}
82
83impl Lifecycle for GroupingMut<'_> {
84    fn on_create(&mut self, _authority: Option<&Pubkey>) -> Result<(), super::Error> {
85        if *self.size > 0 {
86            Err(Error::InvalidGroupSize)
87        } else {
88            Ok(())
89        }
90    }
91
92    fn on_update(&mut self, other: &mut Self, _authority: Option<&Pubkey>) -> Result<(), Error> {
93        // size cannot be updated
94        *other.size = *self.size;
95
96        if let Some(max_size) = other.max_size.value() {
97            // it cannot update the max size to be lower than the current size
98            if **max_size < *other.size {
99                return Err(Error::InvalidMaximumGroupSize(*other.size, **max_size));
100            }
101        }
102
103        Ok(())
104    }
105}
106
107#[repr(C)]
108#[derive(Clone, Copy, Debug, Default, Pod, Zeroable)]
109pub struct NullableU64(u64);
110
111impl NullableU64 {
112    pub fn new(value: u64) -> Self {
113        Self(value)
114    }
115}
116
117impl Deref for NullableU64 {
118    type Target = u64;
119
120    fn deref(&self) -> &Self::Target {
121        &self.0
122    }
123}
124
125impl DerefMut for NullableU64 {
126    fn deref_mut(&mut self) -> &mut Self::Target {
127        &mut self.0
128    }
129}
130
131impl Nullable for NullableU64 {
132    fn is_some(&self) -> bool {
133        self.0 != 0
134    }
135
136    fn is_none(&self) -> bool {
137        self.0 == 0
138    }
139}
140
141/// Builder for a `Group` extension.
142pub struct GroupingBuilder(Vec<u8>);
143
144impl Default for GroupingBuilder {
145    fn default() -> Self {
146        Self(vec![
147            0;
148            (std::mem::size_of::<u64>() * 2)
149                + std::mem::size_of::<Pubkey>()
150        ])
151    }
152}
153
154impl GroupingBuilder {
155    pub fn with_buffer(buffer: Vec<u8>) -> Self {
156        Self(buffer)
157    }
158
159    /// Add a new attribute to the extension.
160    pub fn set(&mut self, max_size: Option<u64>, delegate: Option<&Pubkey>) -> &mut Self {
161        // setting the data replaces any existing data
162        self.0.clear();
163
164        self.0.extend_from_slice(&u64::to_le_bytes(0));
165        self.0
166            .extend_from_slice(&u64::to_le_bytes(max_size.unwrap_or(0)));
167
168        if let Some(delegate) = delegate {
169            self.0.extend_from_slice(delegate.as_ref());
170        } else {
171            self.0.extend_from_slice(Pubkey::default().as_ref());
172        }
173
174        self
175    }
176}
177
178impl<'a> ExtensionBuilder<'a, Grouping<'a>> for GroupingBuilder {
179    fn build(&'a self) -> Grouping<'a> {
180        Grouping::from_bytes(&self.0)
181    }
182
183    fn data(&mut self) -> Vec<u8> {
184        std::mem::take(&mut self.0)
185    }
186}
187
188impl Deref for GroupingBuilder {
189    type Target = Vec<u8>;
190
191    fn deref(&self) -> &Self::Target {
192        &self.0
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use solana_program::sysvar;
199    use std::ops::Deref;
200
201    use crate::extensions::{ExtensionBuilder, GroupingBuilder};
202
203    #[test]
204    fn test_set_max_size() {
205        // max_size set
206        let mut builder = GroupingBuilder::default();
207        builder.set(Some(10), None);
208        let grouping = builder.build();
209
210        assert_eq!(*grouping.size, 0);
211        assert!(grouping.max_size.value().is_some());
212
213        let max_size = grouping.max_size.value().unwrap();
214        assert_eq!(**max_size, 10);
215
216        // "default" max size
217
218        let builder = GroupingBuilder::default();
219        let grouping = builder.build();
220
221        assert_eq!(*grouping.size, 0);
222        assert!(grouping.max_size.value().is_none());
223        assert!(grouping.delegate.value().is_none());
224    }
225
226    #[test]
227    fn test_set_delegate() {
228        // set delegate to a pubkey
229        let mut builder = GroupingBuilder::default();
230        builder.set(None, Some(&sysvar::ID));
231        let grouping = builder.build();
232
233        assert!(grouping.delegate.value().is_some());
234
235        if let Some(delegate) = grouping.delegate.value() {
236            assert_eq!(delegate.deref(), &sysvar::ID);
237        }
238
239        // set delegate to None
240        builder.set(None, None);
241        let grouping = builder.build();
242
243        assert!(grouping.delegate.value().is_none());
244    }
245}