1use super::OnChainIdentity;
5
6use crate::rebased::iota::move_calls;
7use crate::IotaDID;
8
9use crate::rebased::iota::move_calls::ControllerTokenRef;
10use crate::rebased::iota::package::identity_package_id;
11use crate::rebased::Error;
12use async_trait::async_trait;
13use iota_interaction::move_types::language_storage::TypeTag;
14use iota_interaction::rpc_types::IotaExecutionStatus;
15use iota_interaction::rpc_types::IotaTransactionBlockEffects;
16use iota_interaction::rpc_types::IotaTransactionBlockEffectsAPI;
17use iota_interaction::types::base_types::IotaAddress;
18use iota_interaction::types::base_types::ObjectID;
19use iota_interaction::types::id::UID;
20use iota_interaction::types::object::Owner;
21use iota_interaction::types::transaction::ProgrammableTransaction;
22use iota_interaction::IotaTransactionBlockEffectsMutAPI;
23use iota_interaction::MoveType;
24use iota_interaction::OptionalSync;
25use itertools::Itertools as _;
26use product_common::core_client::CoreClientReadOnly;
27use product_common::transaction::transaction_builder::Transaction;
28use product_common::transaction::transaction_builder::TransactionBuilder;
29use serde::Deserialize;
30use serde::Deserializer;
31use serde::Serialize;
32use std::fmt::Display;
33use std::ops::BitAnd;
34use std::ops::BitAndAssign;
35use std::ops::BitOr;
36use std::ops::BitOrAssign;
37use std::ops::BitXor;
38use std::ops::BitXorAssign;
39use std::ops::Not;
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43#[serde(untagged)]
44pub enum ControllerToken {
45 Controller(ControllerCap),
47 Delegate(DelegationToken),
49}
50
51impl ControllerToken {
52 pub fn id(&self) -> ObjectID {
54 match self {
55 Self::Controller(controller) => controller.id,
56 Self::Delegate(delegate) => delegate.id,
57 }
58 }
59
60 pub fn controller_id(&self) -> ObjectID {
64 match self {
65 Self::Controller(controller) => controller.id,
66 Self::Delegate(delegate) => delegate.controller,
67 }
68 }
69
70 pub fn controller_of(&self) -> ObjectID {
72 match self {
73 Self::Controller(controller) => controller.controller_of,
74 Self::Delegate(delegate) => delegate.controller_of,
75 }
76 }
77
78 pub fn as_controller(&self) -> Option<&ControllerCap> {
80 match self {
81 Self::Controller(controller) => Some(controller),
82 Self::Delegate(_) => None,
83 }
84 }
85
86 pub fn try_controller(self) -> Option<ControllerCap> {
88 match self {
89 Self::Controller(controller) => Some(controller),
90 Self::Delegate(_) => None,
91 }
92 }
93
94 pub fn as_delegate(&self) -> Option<&DelegationToken> {
96 match self {
97 Self::Controller(_) => None,
98 Self::Delegate(delegate) => Some(delegate),
99 }
100 }
101
102 pub fn try_delegate(self) -> Option<DelegationToken> {
104 match self {
105 Self::Controller(_) => None,
106 Self::Delegate(delegate) => Some(delegate),
107 }
108 }
109
110 pub fn move_type(&self, package: ObjectID) -> TypeTag {
112 match self {
113 Self::Controller(_) => ControllerCap::move_type(package),
114 Self::Delegate(_) => DelegationToken::move_type(package),
115 }
116 }
117
118 pub(crate) async fn controller_ref<C>(&self, client: &C) -> Result<ControllerTokenRef, Error>
119 where
120 C: CoreClientReadOnly + OptionalSync,
121 {
122 let obj_ref = client
123 .get_object_ref_by_id(self.id())
124 .await?
125 .expect("token exists on-chain")
126 .reference
127 .to_object_ref();
128
129 Ok(match self {
130 Self::Controller(_) => ControllerTokenRef::Controller(obj_ref),
131 Self::Delegate(_) => ControllerTokenRef::Delegate(obj_ref),
132 })
133 }
134
135 #[inline(always)]
136 fn as_type_name(&self) -> &'static str {
137 match self {
138 Self::Controller(_) => "ControllerCap",
139 Self::Delegate(_) => "DelegationToken",
140 }
141 }
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146#[serde(from = "IotaControllerCap")]
147pub struct ControllerCap {
148 id: ObjectID,
149 controller_of: ObjectID,
150 can_delegate: bool,
151}
152
153fn deserialize_from_uid<'de, D>(deserializer: D) -> Result<ObjectID, D::Error>
154where
155 D: Deserializer<'de>,
156{
157 UID::deserialize(deserializer).map(|uid| *uid.object_id())
158}
159
160impl MoveType for ControllerCap {
161 fn move_type(package: ObjectID) -> TypeTag {
162 format!("{package}::controller::ControllerCap")
163 .parse()
164 .expect("valid Move type")
165 }
166}
167
168impl ControllerCap {
169 pub fn id(&self) -> ObjectID {
171 self.id
172 }
173
174 pub fn controller_of(&self) -> ObjectID {
176 self.controller_of
177 }
178
179 pub fn can_delegate(&self) -> bool {
182 self.can_delegate
183 }
184
185 pub fn delegate(
189 &self,
190 recipient: IotaAddress,
191 permissions: Option<DelegatePermissions>,
192 ) -> Option<TransactionBuilder<DelegateToken>> {
193 if !self.can_delegate {
194 return None;
195 }
196
197 let tx = {
198 let permissions = permissions.unwrap_or_default();
199 DelegateToken::new_with_permissions(self, recipient, permissions)
200 };
201
202 Some(TransactionBuilder::new(tx))
203 }
204}
205
206impl From<ControllerCap> for ControllerToken {
207 fn from(cap: ControllerCap) -> Self {
208 Self::Controller(cap)
209 }
210}
211
212#[derive(Debug, Deserialize)]
213struct IotaControllerCap {
214 id: UID,
215 controller_of: ObjectID,
216 can_delegate: bool,
217 #[allow(unused)]
218 access_token: Referent<DelegationToken>,
219}
220
221impl From<IotaControllerCap> for ControllerCap {
222 fn from(value: IotaControllerCap) -> Self {
223 Self {
224 id: *value.id.object_id(),
225 controller_of: value.controller_of,
226 can_delegate: value.can_delegate,
227 }
228 }
229}
230
231#[derive(Debug, Deserialize)]
232#[allow(unused)]
233struct Referent<T> {
234 id: IotaAddress,
235 value: Option<T>,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct DelegationToken {
242 #[serde(deserialize_with = "deserialize_from_uid")]
243 id: ObjectID,
244 permissions: DelegatePermissions,
245 controller: ObjectID,
246 controller_of: ObjectID,
247}
248
249impl DelegationToken {
250 pub fn id(&self) -> ObjectID {
252 self.id
253 }
254
255 pub fn controller(&self) -> ObjectID {
258 self.controller
259 }
260
261 pub fn controller_of(&self) -> ObjectID {
263 self.controller_of
264 }
265
266 pub fn permissions(&self) -> DelegatePermissions {
268 self.permissions
269 }
270}
271
272impl From<DelegationToken> for ControllerToken {
273 fn from(value: DelegationToken) -> Self {
274 Self::Delegate(value)
275 }
276}
277
278impl MoveType for DelegationToken {
279 fn move_type(package: ObjectID) -> TypeTag {
280 format!("{package}::controller::DelegationToken")
281 .parse()
282 .expect("valid Move type")
283 }
284}
285
286#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
296#[serde(transparent)]
297pub struct DelegatePermissions(u32);
298
299impl Default for DelegatePermissions {
300 fn default() -> Self {
301 Self(u32::MAX)
302 }
303}
304
305impl From<u32> for DelegatePermissions {
306 fn from(value: u32) -> Self {
307 Self(value)
308 }
309}
310
311impl From<DelegatePermissions> for u32 {
312 fn from(value: DelegatePermissions) -> Self {
313 value.0
314 }
315}
316
317impl DelegatePermissions {
318 pub const NONE: Self = Self(0);
320 pub const CREATE_PROPOSAL: Self = Self(1);
322 pub const APPROVE_PROPOSAL: Self = Self(1 << 1);
324 pub const EXECUTE_PROPOSAL: Self = Self(1 << 2);
326 pub const DELETE_PROPOSAL: Self = Self(1 << 3);
328 pub const REMOVE_APPROVAL: Self = Self(1 << 4);
330 pub const ALL: Self = Self(u32::MAX);
332
333 pub fn has(&self, permission: Self) -> bool {
344 *self | permission != Self::NONE
345 }
346}
347
348impl Not for DelegatePermissions {
349 type Output = Self;
350 fn not(self) -> Self::Output {
351 Self(!self.0)
352 }
353}
354impl BitOr for DelegatePermissions {
355 type Output = Self;
356 fn bitor(self, rhs: Self) -> Self::Output {
357 Self(self.0 | rhs.0)
358 }
359}
360impl BitOrAssign for DelegatePermissions {
361 fn bitor_assign(&mut self, rhs: Self) {
362 self.0 |= rhs.0;
363 }
364}
365impl BitAnd for DelegatePermissions {
366 type Output = Self;
367 fn bitand(self, rhs: Self) -> Self::Output {
368 Self(self.0 & rhs.0)
369 }
370}
371impl BitAndAssign for DelegatePermissions {
372 fn bitand_assign(&mut self, rhs: Self) {
373 self.0 &= rhs.0;
374 }
375}
376impl BitXor for DelegatePermissions {
377 type Output = Self;
378 fn bitxor(self, rhs: Self) -> Self::Output {
379 Self(self.0 ^ rhs.0)
380 }
381}
382impl BitXorAssign for DelegatePermissions {
383 fn bitxor_assign(&mut self, rhs: Self) {
384 self.0 ^= rhs.0;
385 }
386}
387
388#[derive(Debug, Clone)]
391pub struct DelegateToken {
392 cap_id: ObjectID,
393 permissions: DelegatePermissions,
394 recipient: IotaAddress,
395}
396
397impl DelegateToken {
398 pub fn new(controller_cap: &ControllerCap, recipient: IotaAddress) -> Self {
401 Self::new_with_permissions(controller_cap, recipient, DelegatePermissions::default())
402 }
403
404 pub fn new_with_permissions(
406 controller_cap: &ControllerCap,
407 recipient: IotaAddress,
408 permissions: DelegatePermissions,
409 ) -> Self {
410 Self {
411 cap_id: controller_cap.id(),
412 permissions,
413 recipient,
414 }
415 }
416}
417
418#[cfg_attr(feature = "send-sync", async_trait)]
419#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
420impl Transaction for DelegateToken {
421 type Output = DelegationToken;
422 type Error = Error;
423 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
424 where
425 C: CoreClientReadOnly + OptionalSync,
426 {
427 let package = identity_package_id(client).await?;
428 let controller_cap_ref = client
429 .get_object_ref_by_id(self.cap_id)
430 .await?
431 .expect("ControllerCap exists on-chain")
432 .reference
433 .to_object_ref();
434
435 let ptb_bcs =
436 move_calls::identity::delegate_controller_cap(controller_cap_ref, self.recipient, self.permissions.0, package)
437 .await?;
438 Ok(bcs::from_bytes(&ptb_bcs)?)
439 }
440
441 async fn apply<C>(self, effects: &mut IotaTransactionBlockEffects, client: &C) -> Result<Self::Output, Self::Error>
442 where
443 C: CoreClientReadOnly + OptionalSync,
444 {
445 if let IotaExecutionStatus::Failure { error } = effects.status() {
446 return Err(Error::TransactionUnexpectedResponse(error.clone()));
447 }
448
449 let created_objects = effects
450 .created()
451 .iter()
452 .enumerate()
453 .filter(|(_, elem)| matches!(elem.owner, Owner::AddressOwner(addr) if addr == self.recipient))
454 .map(|(i, obj)| (i, obj.object_id()));
455
456 let is_target_token = |delegation_token: &DelegationToken| -> bool {
457 delegation_token.controller == self.cap_id && delegation_token.permissions == self.permissions
458 };
459 let mut target_token_pos = None;
460 let mut target_token = None;
461 for (i, obj_id) in created_objects {
462 match client.get_object_by_id(obj_id).await {
463 Ok(token) if is_target_token(&token) => {
464 target_token_pos = Some(i);
465 target_token = Some(token);
466 break;
467 }
468 _ => continue,
469 }
470 }
471
472 let (Some(i), Some(token)) = (target_token_pos, target_token) else {
473 return Err(Error::TransactionUnexpectedResponse(
474 "failed to find the correct identity in this transaction's effects".to_owned(),
475 ));
476 };
477
478 effects.created_mut().swap_remove(i);
479
480 Ok(token)
481 }
482}
483
484#[derive(Debug, Clone)]
486pub struct DelegationTokenRevocation {
487 identity_id: ObjectID,
488 controller_cap_id: ObjectID,
489 delegation_token_id: ObjectID,
490 revoke: bool,
492}
493
494impl DelegationTokenRevocation {
495 fn revocation_impl(
496 identity: &OnChainIdentity,
497 controller_cap: &ControllerCap,
498 delegation_token: &DelegationToken,
499 is_revocation: bool,
500 ) -> Result<Self, Error> {
501 if delegation_token.controller_of != identity.id() {
502 return Err(Error::Identity(format!(
503 "DelegationToken {} has no control over Identity {}",
504 delegation_token.id,
505 identity.id()
506 )));
507 }
508
509 Ok(Self {
510 identity_id: identity.id(),
511 controller_cap_id: controller_cap.id(),
512 delegation_token_id: delegation_token.id,
513 revoke: is_revocation,
514 })
515 }
516 pub fn revoke(
518 identity: &OnChainIdentity,
519 controller_cap: &ControllerCap,
520 delegation_token: &DelegationToken,
521 ) -> Result<Self, Error> {
522 Self::revocation_impl(identity, controller_cap, delegation_token, true)
523 }
524
525 pub fn unrevoke(
527 identity: &OnChainIdentity,
528 controller_cap: &ControllerCap,
529 delegation_token: &DelegationToken,
530 ) -> Result<Self, Error> {
531 Self::revocation_impl(identity, controller_cap, delegation_token, false)
532 }
533
534 pub fn is_revocation(&self) -> bool {
536 self.revoke
537 }
538
539 pub fn token_id(&self) -> ObjectID {
541 self.delegation_token_id
542 }
543}
544
545#[cfg_attr(feature = "send-sync", async_trait)]
546#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
547impl Transaction for DelegationTokenRevocation {
548 type Output = ();
549 type Error = Error;
550
551 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
552 where
553 C: CoreClientReadOnly + OptionalSync,
554 {
555 let package = identity_package_id(client).await?;
556 let identity_ref = client
557 .get_object_ref_by_id(self.identity_id)
558 .await?
559 .expect("identity exists on-chain");
560 let controller_cap_ref = client
561 .get_object_ref_by_id(self.controller_cap_id)
562 .await?
563 .expect("controller_cap exists on-chain")
564 .reference
565 .to_object_ref();
566
567 let tx_bytes = if self.is_revocation() {
568 move_calls::identity::revoke_delegation_token(identity_ref, controller_cap_ref, self.delegation_token_id, package)
569 } else {
570 move_calls::identity::unrevoke_delegation_token(
571 identity_ref,
572 controller_cap_ref,
573 self.delegation_token_id,
574 package,
575 )
576 }?;
577
578 Ok(bcs::from_bytes(&tx_bytes)?)
579 }
580
581 async fn apply<C>(self, effects: &mut IotaTransactionBlockEffects, _client: &C) -> Result<Self::Output, Self::Error>
582 where
583 C: CoreClientReadOnly + OptionalSync,
584 {
585 if let IotaExecutionStatus::Failure { error } = effects.status() {
586 return Err(Error::TransactionUnexpectedResponse(error.clone()));
587 }
588
589 Ok(())
590 }
591}
592
593#[derive(Debug, Clone)]
595pub struct DeleteDelegationToken {
596 identity_id: ObjectID,
597 delegation_token_id: ObjectID,
598}
599
600impl DeleteDelegationToken {
601 pub fn new(identity: &OnChainIdentity, delegation_token: DelegationToken) -> Result<Self, Error> {
603 if identity.id() != delegation_token.controller_of {
604 return Err(Error::Identity(format!(
605 "DelegationToken {} has no control over Identity {}",
606 delegation_token.id,
607 identity.id()
608 )));
609 }
610
611 Ok(Self {
612 identity_id: identity.id(),
613 delegation_token_id: delegation_token.id,
614 })
615 }
616
617 pub fn token_id(&self) -> ObjectID {
619 self.delegation_token_id
620 }
621}
622
623#[cfg_attr(feature = "send-sync", async_trait)]
624#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
625impl Transaction for DeleteDelegationToken {
626 type Output = ();
627 type Error = Error;
628
629 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
630 where
631 C: CoreClientReadOnly + OptionalSync,
632 {
633 let package = identity_package_id(client).await?;
634 let identity_ref = client
635 .get_object_ref_by_id(self.identity_id)
636 .await?
637 .ok_or_else(|| Error::ObjectLookup(format!("Identity {} doesn't exist on-chain", self.identity_id)))?;
638 let delegation_token_ref = client
639 .get_object_ref_by_id(self.delegation_token_id)
640 .await?
641 .ok_or_else(|| {
642 Error::ObjectLookup(format!(
643 "DelegationToken {} doesn't exist on-chain",
644 self.delegation_token_id,
645 ))
646 })?
647 .reference
648 .to_object_ref();
649
650 let tx_bytes = move_calls::identity::destroy_delegation_token(identity_ref, delegation_token_ref, package).await?;
651
652 Ok(bcs::from_bytes(&tx_bytes)?)
653 }
654
655 async fn apply<C>(self, effects: &mut IotaTransactionBlockEffects, _client: &C) -> Result<Self::Output, Self::Error>
656 where
657 C: CoreClientReadOnly + OptionalSync,
658 {
659 if let IotaExecutionStatus::Failure { error } = effects.status() {
660 return Err(Error::TransactionUnexpectedResponse(error.clone()));
661 }
662
663 let Some(deleted_token_pos) = effects
664 .deleted()
665 .iter()
666 .find_position(|obj_ref| obj_ref.object_id == self.delegation_token_id)
667 .map(|(pos, _)| pos)
668 else {
669 return Err(Error::TransactionUnexpectedResponse(format!(
670 "DelegationToken {} wasn't deleted in this transaction",
671 self.delegation_token_id,
672 )));
673 };
674
675 effects.deleted_mut().swap_remove(deleted_token_pos);
676
677 Ok(())
678 }
679}
680
681#[derive(Debug, thiserror::Error)]
684#[error("address '{address}' is not a controller of '{identity}'")]
685#[non_exhaustive]
686pub struct NotAController {
687 pub address: IotaAddress,
689 pub identity: IotaDID,
691}
692
693#[derive(Debug, thiserror::Error)]
695#[error(
696 "controller '{controller_token_id}' has a voting power of {controller_voting_power}, but {required} is required"
697)]
698#[non_exhaustive]
699pub struct InsufficientControllerVotingPower {
700 pub controller_token_id: ObjectID,
702 pub controller_voting_power: u64,
704 pub required: u64,
706}
707
708#[derive(Debug, thiserror::Error)]
711#[non_exhaustive]
712pub struct InvalidControllerTokenForIdentity {
713 pub identity: ObjectID,
715 pub controller_token: ControllerToken,
717}
718
719impl Display for InvalidControllerTokenForIdentity {
720 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
721 let token_type = self.controller_token.as_type_name();
722 let token_id = self.controller_token.id();
723 let identity_id = self.identity;
724
725 write!(
726 f,
727 "the presented {token_type} `{token_id}` does not grant access to Identity `{identity_id}`"
728 )
729 }
730}