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::ops::BitAnd;
32use std::ops::BitAndAssign;
33use std::ops::BitOr;
34use std::ops::BitOrAssign;
35use std::ops::BitXor;
36use std::ops::BitXorAssign;
37use std::ops::Not;
38#[derive(Debug, Clone, Serialize, Deserialize)]
40#[serde(untagged)]
41pub enum ControllerToken {
42 Controller(ControllerCap),
44 Delegate(DelegationToken),
46}
47
48impl ControllerToken {
49 pub fn id(&self) -> ObjectID {
51 match self {
52 Self::Controller(controller) => controller.id,
53 Self::Delegate(delegate) => delegate.id,
54 }
55 }
56
57 pub fn controller_id(&self) -> ObjectID {
61 match self {
62 Self::Controller(controller) => controller.id,
63 Self::Delegate(delegate) => delegate.controller,
64 }
65 }
66
67 pub fn controller_of(&self) -> ObjectID {
69 match self {
70 Self::Controller(controller) => controller.controller_of,
71 Self::Delegate(delegate) => delegate.controller_of,
72 }
73 }
74
75 pub fn as_controller(&self) -> Option<&ControllerCap> {
77 match self {
78 Self::Controller(controller) => Some(controller),
79 Self::Delegate(_) => None,
80 }
81 }
82
83 pub fn try_controller(self) -> Option<ControllerCap> {
85 match self {
86 Self::Controller(controller) => Some(controller),
87 Self::Delegate(_) => None,
88 }
89 }
90
91 pub fn as_delegate(&self) -> Option<&DelegationToken> {
93 match self {
94 Self::Controller(_) => None,
95 Self::Delegate(delegate) => Some(delegate),
96 }
97 }
98
99 pub fn try_delegate(self) -> Option<DelegationToken> {
101 match self {
102 Self::Controller(_) => None,
103 Self::Delegate(delegate) => Some(delegate),
104 }
105 }
106
107 pub fn move_type(&self, package: ObjectID) -> TypeTag {
109 match self {
110 Self::Controller(_) => ControllerCap::move_type(package),
111 Self::Delegate(_) => DelegationToken::move_type(package),
112 }
113 }
114
115 pub(crate) async fn controller_ref<C>(&self, client: &C) -> Result<ControllerTokenRef, Error>
116 where
117 C: CoreClientReadOnly + OptionalSync,
118 {
119 let obj_ref = client
120 .get_object_ref_by_id(self.id())
121 .await?
122 .expect("token exists on-chain")
123 .reference
124 .to_object_ref();
125
126 Ok(match self {
127 Self::Controller(_) => ControllerTokenRef::Controller(obj_ref),
128 Self::Delegate(_) => ControllerTokenRef::Delegate(obj_ref),
129 })
130 }
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct ControllerCap {
136 #[serde(deserialize_with = "deserialize_from_uid")]
137 id: ObjectID,
138 controller_of: ObjectID,
139 can_delegate: bool,
140}
141
142fn deserialize_from_uid<'de, D>(deserializer: D) -> Result<ObjectID, D::Error>
143where
144 D: Deserializer<'de>,
145{
146 UID::deserialize(deserializer).map(|uid| *uid.object_id())
147}
148
149impl MoveType for ControllerCap {
150 fn move_type(package: ObjectID) -> TypeTag {
151 format!("{package}::controller::ControllerCap")
152 .parse()
153 .expect("valid Move type")
154 }
155}
156
157impl ControllerCap {
158 pub fn id(&self) -> ObjectID {
160 self.id
161 }
162
163 pub fn controller_of(&self) -> ObjectID {
165 self.controller_of
166 }
167
168 pub fn can_delegate(&self) -> bool {
171 self.can_delegate
172 }
173
174 pub fn delegate(
178 &self,
179 recipient: IotaAddress,
180 permissions: Option<DelegatePermissions>,
181 ) -> Option<TransactionBuilder<DelegateToken>> {
182 if !self.can_delegate {
183 return None;
184 }
185
186 let tx = {
187 let permissions = permissions.unwrap_or_default();
188 DelegateToken::new_with_permissions(self, recipient, permissions)
189 };
190
191 Some(TransactionBuilder::new(tx))
192 }
193}
194
195impl From<ControllerCap> for ControllerToken {
196 fn from(cap: ControllerCap) -> Self {
197 Self::Controller(cap)
198 }
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct DelegationToken {
205 #[serde(deserialize_with = "deserialize_from_uid")]
206 id: ObjectID,
207 permissions: DelegatePermissions,
208 controller: ObjectID,
209 controller_of: ObjectID,
210}
211
212impl DelegationToken {
213 pub fn id(&self) -> ObjectID {
215 self.id
216 }
217
218 pub fn controller(&self) -> ObjectID {
221 self.controller
222 }
223
224 pub fn controller_of(&self) -> ObjectID {
226 self.controller_of
227 }
228
229 pub fn permissions(&self) -> DelegatePermissions {
231 self.permissions
232 }
233}
234
235impl From<DelegationToken> for ControllerToken {
236 fn from(value: DelegationToken) -> Self {
237 Self::Delegate(value)
238 }
239}
240
241impl MoveType for DelegationToken {
242 fn move_type(package: ObjectID) -> TypeTag {
243 format!("{package}::controller::DelegationToken")
244 .parse()
245 .expect("valid Move type")
246 }
247}
248
249#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
259#[serde(transparent)]
260pub struct DelegatePermissions(u32);
261
262impl Default for DelegatePermissions {
263 fn default() -> Self {
264 Self(u32::MAX)
265 }
266}
267
268impl From<u32> for DelegatePermissions {
269 fn from(value: u32) -> Self {
270 Self(value)
271 }
272}
273
274impl From<DelegatePermissions> for u32 {
275 fn from(value: DelegatePermissions) -> Self {
276 value.0
277 }
278}
279
280impl DelegatePermissions {
281 pub const NONE: Self = Self(0);
283 pub const CREATE_PROPOSAL: Self = Self(1);
285 pub const APPROVE_PROPOSAL: Self = Self(1 << 1);
287 pub const EXECUTE_PROPOSAL: Self = Self(1 << 2);
289 pub const DELETE_PROPOSAL: Self = Self(1 << 3);
291 pub const REMOVE_APPROVAL: Self = Self(1 << 4);
293 pub const ALL: Self = Self(u32::MAX);
295
296 pub fn has(&self, permission: Self) -> bool {
307 *self | permission != Self::NONE
308 }
309}
310
311impl Not for DelegatePermissions {
312 type Output = Self;
313 fn not(self) -> Self::Output {
314 Self(!self.0)
315 }
316}
317impl BitOr for DelegatePermissions {
318 type Output = Self;
319 fn bitor(self, rhs: Self) -> Self::Output {
320 Self(self.0 | rhs.0)
321 }
322}
323impl BitOrAssign for DelegatePermissions {
324 fn bitor_assign(&mut self, rhs: Self) {
325 self.0 |= rhs.0;
326 }
327}
328impl BitAnd for DelegatePermissions {
329 type Output = Self;
330 fn bitand(self, rhs: Self) -> Self::Output {
331 Self(self.0 & rhs.0)
332 }
333}
334impl BitAndAssign for DelegatePermissions {
335 fn bitand_assign(&mut self, rhs: Self) {
336 self.0 &= rhs.0;
337 }
338}
339impl BitXor for DelegatePermissions {
340 type Output = Self;
341 fn bitxor(self, rhs: Self) -> Self::Output {
342 Self(self.0 ^ rhs.0)
343 }
344}
345impl BitXorAssign for DelegatePermissions {
346 fn bitxor_assign(&mut self, rhs: Self) {
347 self.0 ^= rhs.0;
348 }
349}
350
351#[derive(Debug, Clone)]
354pub struct DelegateToken {
355 cap_id: ObjectID,
356 permissions: DelegatePermissions,
357 recipient: IotaAddress,
358}
359
360impl DelegateToken {
361 pub fn new(controller_cap: &ControllerCap, recipient: IotaAddress) -> Self {
364 Self::new_with_permissions(controller_cap, recipient, DelegatePermissions::default())
365 }
366
367 pub fn new_with_permissions(
369 controller_cap: &ControllerCap,
370 recipient: IotaAddress,
371 permissions: DelegatePermissions,
372 ) -> Self {
373 Self {
374 cap_id: controller_cap.id(),
375 permissions,
376 recipient,
377 }
378 }
379}
380
381#[cfg_attr(feature = "send-sync", async_trait)]
382#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
383impl Transaction for DelegateToken {
384 type Output = DelegationToken;
385 type Error = Error;
386 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
387 where
388 C: CoreClientReadOnly + OptionalSync,
389 {
390 let package = identity_package_id(client).await?;
391 let controller_cap_ref = client
392 .get_object_ref_by_id(self.cap_id)
393 .await?
394 .expect("ControllerCap exists on-chain")
395 .reference
396 .to_object_ref();
397
398 let ptb_bcs =
399 move_calls::identity::delegate_controller_cap(controller_cap_ref, self.recipient, self.permissions.0, package)
400 .await?;
401 Ok(bcs::from_bytes(&ptb_bcs)?)
402 }
403
404 async fn apply<C>(self, effects: &mut IotaTransactionBlockEffects, client: &C) -> Result<Self::Output, Self::Error>
405 where
406 C: CoreClientReadOnly + OptionalSync,
407 {
408 if let IotaExecutionStatus::Failure { error } = effects.status() {
409 return Err(Error::TransactionUnexpectedResponse(error.clone()));
410 }
411
412 let created_objects = effects
413 .created()
414 .iter()
415 .enumerate()
416 .filter(|(_, elem)| matches!(elem.owner, Owner::AddressOwner(addr) if addr == self.recipient))
417 .map(|(i, obj)| (i, obj.object_id()));
418
419 let is_target_token = |delegation_token: &DelegationToken| -> bool {
420 delegation_token.controller == self.cap_id && delegation_token.permissions == self.permissions
421 };
422 let mut target_token_pos = None;
423 let mut target_token = None;
424 for (i, obj_id) in created_objects {
425 match client.get_object_by_id(obj_id).await {
426 Ok(token) if is_target_token(&token) => {
427 target_token_pos = Some(i);
428 target_token = Some(token);
429 break;
430 }
431 _ => continue,
432 }
433 }
434
435 let (Some(i), Some(token)) = (target_token_pos, target_token) else {
436 return Err(Error::TransactionUnexpectedResponse(
437 "failed to find the correct identity in this transaction's effects".to_owned(),
438 ));
439 };
440
441 effects.created_mut().swap_remove(i);
442
443 Ok(token)
444 }
445}
446
447#[derive(Debug, Clone)]
449pub struct DelegationTokenRevocation {
450 identity_id: ObjectID,
451 controller_cap_id: ObjectID,
452 delegation_token_id: ObjectID,
453 revoke: bool,
455}
456
457impl DelegationTokenRevocation {
458 fn revocation_impl(
459 identity: &OnChainIdentity,
460 controller_cap: &ControllerCap,
461 delegation_token: &DelegationToken,
462 is_revocation: bool,
463 ) -> Result<Self, Error> {
464 if delegation_token.controller_of != identity.id() {
465 return Err(Error::Identity(format!(
466 "DelegationToken {} has no control over Identity {}",
467 delegation_token.id,
468 identity.id()
469 )));
470 }
471
472 Ok(Self {
473 identity_id: identity.id(),
474 controller_cap_id: controller_cap.id(),
475 delegation_token_id: delegation_token.id,
476 revoke: is_revocation,
477 })
478 }
479 pub fn revoke(
481 identity: &OnChainIdentity,
482 controller_cap: &ControllerCap,
483 delegation_token: &DelegationToken,
484 ) -> Result<Self, Error> {
485 Self::revocation_impl(identity, controller_cap, delegation_token, true)
486 }
487
488 pub fn unrevoke(
490 identity: &OnChainIdentity,
491 controller_cap: &ControllerCap,
492 delegation_token: &DelegationToken,
493 ) -> Result<Self, Error> {
494 Self::revocation_impl(identity, controller_cap, delegation_token, false)
495 }
496
497 pub fn is_revocation(&self) -> bool {
499 self.revoke
500 }
501
502 pub fn token_id(&self) -> ObjectID {
504 self.delegation_token_id
505 }
506}
507
508#[cfg_attr(feature = "send-sync", async_trait)]
509#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
510impl Transaction for DelegationTokenRevocation {
511 type Output = ();
512 type Error = Error;
513
514 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
515 where
516 C: CoreClientReadOnly + OptionalSync,
517 {
518 let package = identity_package_id(client).await?;
519 let identity_ref = client
520 .get_object_ref_by_id(self.identity_id)
521 .await?
522 .expect("identity exists on-chain");
523 let controller_cap_ref = client
524 .get_object_ref_by_id(self.controller_cap_id)
525 .await?
526 .expect("controller_cap exists on-chain")
527 .reference
528 .to_object_ref();
529
530 let tx_bytes = if self.is_revocation() {
531 move_calls::identity::revoke_delegation_token(identity_ref, controller_cap_ref, self.delegation_token_id, package)
532 } else {
533 move_calls::identity::unrevoke_delegation_token(
534 identity_ref,
535 controller_cap_ref,
536 self.delegation_token_id,
537 package,
538 )
539 }?;
540
541 Ok(bcs::from_bytes(&tx_bytes)?)
542 }
543
544 async fn apply<C>(self, effects: &mut IotaTransactionBlockEffects, _client: &C) -> Result<Self::Output, Self::Error>
545 where
546 C: CoreClientReadOnly + OptionalSync,
547 {
548 if let IotaExecutionStatus::Failure { error } = effects.status() {
549 return Err(Error::TransactionUnexpectedResponse(error.clone()));
550 }
551
552 Ok(())
553 }
554}
555
556#[derive(Debug, Clone)]
558pub struct DeleteDelegationToken {
559 identity_id: ObjectID,
560 delegation_token_id: ObjectID,
561}
562
563impl DeleteDelegationToken {
564 pub fn new(identity: &OnChainIdentity, delegation_token: DelegationToken) -> Result<Self, Error> {
566 if identity.id() != delegation_token.controller_of {
567 return Err(Error::Identity(format!(
568 "DelegationToken {} has no control over Identity {}",
569 delegation_token.id,
570 identity.id()
571 )));
572 }
573
574 Ok(Self {
575 identity_id: identity.id(),
576 delegation_token_id: delegation_token.id,
577 })
578 }
579
580 pub fn token_id(&self) -> ObjectID {
582 self.delegation_token_id
583 }
584}
585
586#[cfg_attr(feature = "send-sync", async_trait)]
587#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
588impl Transaction for DeleteDelegationToken {
589 type Output = ();
590 type Error = Error;
591
592 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
593 where
594 C: CoreClientReadOnly + OptionalSync,
595 {
596 let package = identity_package_id(client).await?;
597 let identity_ref = client
598 .get_object_ref_by_id(self.identity_id)
599 .await?
600 .ok_or_else(|| Error::ObjectLookup(format!("Identity {} doesn't exist on-chain", self.identity_id)))?;
601 let delegation_token_ref = client
602 .get_object_ref_by_id(self.delegation_token_id)
603 .await?
604 .ok_or_else(|| {
605 Error::ObjectLookup(format!(
606 "DelegationToken {} doesn't exist on-chain",
607 self.delegation_token_id,
608 ))
609 })?
610 .reference
611 .to_object_ref();
612
613 let tx_bytes = move_calls::identity::destroy_delegation_token(identity_ref, delegation_token_ref, package).await?;
614
615 Ok(bcs::from_bytes(&tx_bytes)?)
616 }
617
618 async fn apply<C>(self, effects: &mut IotaTransactionBlockEffects, _client: &C) -> Result<Self::Output, Self::Error>
619 where
620 C: CoreClientReadOnly + OptionalSync,
621 {
622 if let IotaExecutionStatus::Failure { error } = effects.status() {
623 return Err(Error::TransactionUnexpectedResponse(error.clone()));
624 }
625
626 let Some(deleted_token_pos) = effects
627 .deleted()
628 .iter()
629 .find_position(|obj_ref| obj_ref.object_id == self.delegation_token_id)
630 .map(|(pos, _)| pos)
631 else {
632 return Err(Error::TransactionUnexpectedResponse(format!(
633 "DelegationToken {} wasn't deleted in this transaction",
634 self.delegation_token_id,
635 )));
636 };
637
638 effects.deleted_mut().swap_remove(deleted_token_pos);
639
640 Ok(())
641 }
642}