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