identity_iota_core/rebased/proposals/
update_did_doc.rs

1// Copyright 2020-2024 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3use iota_interaction::OptionalSync;
4
5use crate::rebased::iota::package::identity_package_id;
6use std::marker::PhantomData;
7
8use crate::rebased::iota::move_calls;
9use crate::rebased::migration::ControllerToken;
10use crate::IotaDocument;
11use async_trait::async_trait;
12use iota_interaction::rpc_types::IotaTransactionBlockEffects;
13use iota_interaction::types::base_types::ObjectID;
14use iota_interaction::types::TypeTag;
15use product_common::core_client::CoreClientReadOnly;
16use product_common::transaction::transaction_builder::TransactionBuilder;
17use serde::Deserialize;
18use serde::Serialize;
19
20use crate::rebased::migration::OnChainIdentity;
21use crate::rebased::migration::Proposal;
22use crate::rebased::Error;
23use iota_interaction::MoveType;
24
25use super::CreateProposal;
26use super::ExecuteProposal;
27use super::ProposalT;
28
29/// Proposal's action for updating a DID Document.
30#[derive(Debug, Clone, Serialize, Deserialize)]
31#[serde(into = "UpdateValue::<Option<Vec<u8>>>", from = "UpdateValue::<Option<Vec<u8>>>")]
32pub struct UpdateDidDocument(Option<Vec<u8>>);
33
34impl MoveType for UpdateDidDocument {
35  fn move_type(package: ObjectID) -> TypeTag {
36    use std::str::FromStr;
37
38    TypeTag::from_str(&format!(
39      "{package}::update_value_proposal::UpdateValue<0x1::option::Option<vector<u8>>>"
40    ))
41    .expect("valid TypeTag")
42  }
43}
44
45impl UpdateDidDocument {
46  /// Creates a new [`UpdateDidDocument`] action.
47  pub fn new(document: IotaDocument) -> Self {
48    Self(Some(document.pack().expect("a valid IotaDocument is packable")))
49  }
50
51  /// Creates a new [`UpdateDidDocument`] action to deactivate the DID Document.
52  pub fn deactivate() -> Self {
53    Self(Some(vec![]))
54  }
55
56  /// Creates a new [`UpdateDidDocument`] action to delete the DID Document.
57  pub fn delete() -> Self {
58    Self(None)
59  }
60
61  /// Returns the serialized DID document bytes.
62  pub fn did_document_bytes(&self) -> Option<&[u8]> {
63    self.0.as_deref()
64  }
65}
66
67#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
68#[cfg_attr(feature = "send-sync", async_trait)]
69impl ProposalT for Proposal<UpdateDidDocument> {
70  type Action = UpdateDidDocument;
71  type Output = ();
72
73  async fn create<'i, C>(
74    action: Self::Action,
75    expiration: Option<u64>,
76    identity: &'i mut OnChainIdentity,
77    controller_token: &ControllerToken,
78    client: &C,
79  ) -> Result<TransactionBuilder<CreateProposal<'i, Self::Action>>, Error>
80  where
81    C: CoreClientReadOnly + OptionalSync,
82  {
83    if identity.id() != controller_token.controller_of() {
84      return Err(Error::Identity(format!(
85        "token {} doesn't grant access to identity {}",
86        controller_token.id(),
87        identity.id()
88      )));
89    }
90    if identity.has_deleted_did() {
91      return Err(Error::Identity("cannot update a deleted DID Document".into()));
92    }
93
94    let package = identity_package_id(client).await?;
95    let identity_ref = client
96      .get_object_ref_by_id(identity.id())
97      .await?
98      .expect("identity exists on-chain");
99    let controller_cap_ref = controller_token.controller_ref(client).await?;
100    let sender_vp = identity
101      .controller_voting_power(controller_token.controller_id())
102      .expect("controller exists");
103    let chained_execution = sender_vp >= identity.threshold();
104    let tx = move_calls::identity::propose_update(
105      identity_ref,
106      controller_cap_ref,
107      action.0.as_deref(),
108      expiration,
109      package,
110    )
111    .await
112    .map_err(|e| Error::TransactionBuildingFailed(e.to_string()))?;
113
114    let ptb = bcs::from_bytes(&tx)?;
115
116    Ok(TransactionBuilder::new(CreateProposal {
117      identity,
118      ptb,
119      chained_execution,
120      _action: PhantomData,
121    }))
122  }
123
124  async fn into_tx<'i, C>(
125    self,
126    identity: &'i mut OnChainIdentity,
127    controller_token: &ControllerToken,
128    client: &C,
129  ) -> Result<TransactionBuilder<ExecuteProposal<'i, Self::Action>>, Error>
130  where
131    C: CoreClientReadOnly + OptionalSync,
132  {
133    if identity.id() != controller_token.controller_of() {
134      return Err(Error::Identity(format!(
135        "token {} doesn't grant access to identity {}",
136        controller_token.id(),
137        identity.id()
138      )));
139    }
140    if identity.has_deleted_did() {
141      return Err(Error::Identity("cannot update a deleted DID Document".into()));
142    }
143
144    let proposal_id = self.id();
145    let identity_ref = client
146      .get_object_ref_by_id(identity.id())
147      .await?
148      .expect("identity exists on-chain");
149    let controller_cap_ref = controller_token.controller_ref(client).await?;
150    let package = identity_package_id(client).await?;
151
152    let tx = move_calls::identity::execute_update(identity_ref, controller_cap_ref, proposal_id, package)
153      .await
154      .map_err(|e| Error::TransactionBuildingFailed(e.to_string()))?;
155
156    let ptb = bcs::from_bytes(&tx)?;
157
158    Ok(TransactionBuilder::new(ExecuteProposal {
159      identity,
160      ptb,
161      _action: PhantomData,
162    }))
163  }
164
165  fn parse_tx_effects(_tx_response: &IotaTransactionBlockEffects) -> Result<Self::Output, Error> {
166    Ok(())
167  }
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
171struct UpdateValue<V> {
172  new_value: V,
173}
174
175impl From<UpdateDidDocument> for UpdateValue<Option<Vec<u8>>> {
176  fn from(value: UpdateDidDocument) -> Self {
177    Self { new_value: value.0 }
178  }
179}
180
181impl From<UpdateValue<Option<Vec<u8>>>> for UpdateDidDocument {
182  fn from(value: UpdateValue<Option<Vec<u8>>>) -> Self {
183    UpdateDidDocument(value.new_value)
184  }
185}