identity_iota_core/rebased/migration/
multicontroller.rs

1// Copyright 2020-2024 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::HashMap;
5use std::collections::HashSet;
6
7use crate::rebased::iota::types::Bag;
8use crate::rebased::iota::types::Number;
9use iota_interaction::types::base_types::ObjectID;
10use iota_interaction::types::collection_types::Entry;
11use iota_interaction::types::collection_types::VecMap;
12use iota_interaction::types::collection_types::VecSet;
13use iota_interaction::types::id::UID;
14use serde::Deserialize;
15use serde::Serialize;
16
17/// A [`Multicontroller`]'s proposal for changes.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(
20  try_from = "IotaProposal::<T>",
21  into = "IotaProposal::<T>",
22  bound(serialize = "T: Serialize + Clone")
23)]
24pub struct Proposal<T> {
25  id: UID,
26  expiration_epoch: Option<u64>,
27  votes: u64,
28  voters: HashSet<ObjectID>,
29  pub(crate) action: T,
30}
31
32impl<T> Proposal<T> {
33  /// Returns this [Proposal]'s ID.
34  pub fn id(&self) -> ObjectID {
35    *self.id.object_id()
36  }
37
38  /// Returns the votes received by this [`Proposal`].
39  pub fn votes(&self) -> u64 {
40    self.votes
41  }
42
43  pub(crate) fn votes_mut(&mut self) -> &mut u64 {
44    &mut self.votes
45  }
46
47  /// Returns a reference to the action contained by this [`Proposal`].
48  pub fn action(&self) -> &T {
49    &self.action
50  }
51
52  /// Consumes the [`Proposal`] returning its action.
53  pub fn into_action(self) -> T {
54    self.action
55  }
56
57  /// Returns the set of voters' IDs.
58  pub fn voters(&self) -> &HashSet<ObjectID> {
59    &self.voters
60  }
61
62  /// Returns the epoch ID for this proposal's expiration.
63  pub fn expiration_epoch(&self) -> Option<u64> {
64    self.expiration_epoch
65  }
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69struct IotaProposal<T> {
70  id: UID,
71  expiration_epoch: Option<Number<u64>>,
72  votes: Number<u64>,
73  voters: VecSet<ObjectID>,
74  action: T,
75}
76
77impl<T> TryFrom<IotaProposal<T>> for Proposal<T> {
78  type Error = <u64 as TryFrom<Number<u64>>>::Error;
79  fn try_from(proposal: IotaProposal<T>) -> Result<Self, Self::Error> {
80    let IotaProposal {
81      id,
82      expiration_epoch,
83      votes,
84      voters,
85      action,
86    } = proposal;
87    let expiration_epoch = expiration_epoch.map(TryInto::try_into).transpose()?;
88    let votes = votes.try_into()?;
89    let voters = voters.contents.into_iter().collect();
90
91    Ok(Self {
92      id,
93      expiration_epoch,
94      votes,
95      voters,
96      action,
97    })
98  }
99}
100
101impl<T> From<Proposal<T>> for IotaProposal<T> {
102  fn from(value: Proposal<T>) -> Self {
103    let Proposal {
104      id,
105      expiration_epoch,
106      votes,
107      voters,
108      action,
109    } = value;
110    let contents = voters.into_iter().collect();
111    IotaProposal {
112      id,
113      expiration_epoch: expiration_epoch.map(Into::into),
114      votes: votes.into(),
115      voters: VecSet { contents },
116      action,
117    }
118  }
119}
120
121/// Representation of `identity.rs`'s `multicontroller::Multicontroller` Move type.
122#[derive(Clone, Debug, Serialize, Deserialize)]
123#[serde(try_from = "IotaMulticontroller::<T>")]
124pub struct Multicontroller<T> {
125  controlled_value: T,
126  controllers: HashMap<ObjectID, u64>,
127  threshold: u64,
128  active_proposals: HashSet<ObjectID>,
129  proposals: Bag,
130}
131
132impl<T> Multicontroller<T> {
133  /// Returns a reference to the value that is shared between many controllers.
134  pub fn controlled_value(&self) -> &T {
135    &self.controlled_value
136  }
137
138  /// Returns this [`Multicontroller`]'s threshold.
139  pub fn threshold(&self) -> u64 {
140    self.threshold
141  }
142
143  /// Returns the lists of active [`Proposal`]s for this [`Multicontroller`].
144  pub fn proposals(&self) -> &HashSet<ObjectID> {
145    &self.active_proposals
146  }
147
148  pub(crate) fn proposals_bag_id(&self) -> ObjectID {
149    *self.proposals.id.object_id()
150  }
151
152  /// Returns the voting power for controller with ID `controller_cap_id`, if any.
153  pub fn controller_voting_power(&self, controller_cap_id: ObjectID) -> Option<u64> {
154    self.controllers.get(&controller_cap_id).copied()
155  }
156
157  /// Consumes this [`Multicontroller`], returning the wrapped value.
158  pub fn into_inner(self) -> T {
159    self.controlled_value
160  }
161
162  pub(crate) fn controllers(&self) -> &HashMap<ObjectID, u64> {
163    &self.controllers
164  }
165
166  /// Returns `true` if `cap_id` is among this [`Multicontroller`]'s controllers' IDs.
167  pub fn has_member(&self, cap_id: ObjectID) -> bool {
168    self.controllers.contains_key(&cap_id)
169  }
170}
171
172impl<T> TryFrom<IotaMulticontroller<T>> for Multicontroller<T> {
173  type Error = <u64 as TryFrom<Number<u64>>>::Error;
174  fn try_from(value: IotaMulticontroller<T>) -> Result<Self, Self::Error> {
175    let IotaMulticontroller {
176      controlled_value,
177      controllers,
178      threshold,
179      active_proposals,
180      proposals,
181    } = value;
182    let controllers = controllers
183      .contents
184      .into_iter()
185      .map(|Entry { key: id, value: vp }| (u64::try_from(vp).map(|vp| (id, vp))))
186      .collect::<Result<_, _>>()?;
187
188    Ok(Multicontroller {
189      controlled_value,
190      controllers,
191      threshold: threshold.try_into()?,
192      active_proposals,
193      proposals,
194    })
195  }
196}
197
198#[derive(Debug, Serialize, Deserialize)]
199struct IotaMulticontroller<T> {
200  controlled_value: T,
201  controllers: VecMap<ObjectID, Number<u64>>,
202  threshold: Number<u64>,
203  active_proposals: HashSet<ObjectID>,
204  proposals: Bag,
205}