identity_iota_core/rebased/proposals/
send.rs1use std::marker::PhantomData;
5
6use crate::rebased::iota::package::identity_package_id;
7use async_trait::async_trait;
8use iota_interaction::rpc_types::IotaTransactionBlockEffects;
9use iota_interaction::types::base_types::IotaAddress;
10use iota_interaction::types::base_types::ObjectID;
11use iota_interaction::types::TypeTag;
12use iota_interaction::MoveType;
13use iota_interaction::OptionalSync;
14use product_common::core_client::CoreClientReadOnly;
15use product_common::transaction::transaction_builder::TransactionBuilder;
16use serde::Deserialize;
17use serde::Serialize;
18
19use crate::rebased::iota::move_calls;
20use crate::rebased::migration::ControllerToken;
21use crate::rebased::migration::OnChainIdentity;
22use crate::rebased::Error;
23
24use super::CreateProposal;
25use super::ExecuteProposal;
26use super::Proposal;
27use super::ProposalBuilder;
28use super::ProposalT;
29
30#[derive(Debug, Clone, Deserialize, Default, Serialize)]
32#[serde(from = "IotaSendAction", into = "IotaSendAction")]
33pub struct SendAction(Vec<(ObjectID, IotaAddress)>);
34
35impl MoveType for SendAction {
36 fn move_type(package: ObjectID) -> TypeTag {
37 use std::str::FromStr;
38
39 TypeTag::from_str(&format!("{package}::transfer_proposal::Send")).expect("valid move type")
40 }
41}
42
43impl SendAction {
44 pub fn send_object(&mut self, object_id: ObjectID, recipient: IotaAddress) {
46 self.0.push((object_id, recipient));
47 }
48
49 pub fn send_objects<I>(&mut self, objects: I)
51 where
52 I: IntoIterator<Item = (ObjectID, IotaAddress)>,
53 {
54 objects
55 .into_iter()
56 .for_each(|(obj_id, recp)| self.send_object(obj_id, recp));
57 }
58}
59
60impl AsRef<[(ObjectID, IotaAddress)]> for SendAction {
61 fn as_ref(&self) -> &[(ObjectID, IotaAddress)] {
62 &self.0
63 }
64}
65
66impl ProposalBuilder<'_, '_, SendAction> {
67 pub fn object(mut self, object_id: ObjectID, recipient: IotaAddress) -> Self {
69 self.send_object(object_id, recipient);
70 self
71 }
72
73 pub fn objects<I>(mut self, objects: I) -> Self
75 where
76 I: IntoIterator<Item = (ObjectID, IotaAddress)>,
77 {
78 self.send_objects(objects);
79 self
80 }
81}
82
83#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
84#[cfg_attr(feature = "send-sync", async_trait)]
85impl ProposalT for Proposal<SendAction> {
86 type Action = SendAction;
87 type Output = ();
88
89 async fn create<'i, C>(
90 action: Self::Action,
91 expiration: Option<u64>,
92 identity: &'i mut OnChainIdentity,
93 controller_token: &ControllerToken,
94 client: &C,
95 ) -> Result<TransactionBuilder<CreateProposal<'i, Self::Action>>, Error>
96 where
97 C: CoreClientReadOnly + OptionalSync,
98 {
99 if identity.id() != controller_token.controller_of() {
100 return Err(Error::Identity(format!(
101 "token {} doesn't grant access to identity {}",
102 controller_token.id(),
103 identity.id()
104 )));
105 }
106 let package = identity_package_id(client).await?;
107 let identity_ref = client
108 .get_object_ref_by_id(identity.id())
109 .await?
110 .expect("identity exists on-chain");
111 let controller_cap_ref = controller_token.controller_ref(client).await?;
112 let can_execute = identity
113 .controller_voting_power(controller_token.controller_id())
114 .expect("controller_cap is for this identity")
115 >= identity.threshold();
116 let tx = if can_execute {
117 let object_type_list = {
119 let ids = action.0.iter().map(|(obj_id, _rcp)| obj_id);
120 let mut object_and_type_list = vec![];
121 for obj_id in ids {
122 let ref_and_type = super::obj_ref_and_type_for_id(client, *obj_id)
123 .await
124 .map_err(|e| Error::ObjectLookup(e.to_string()))?;
125 object_and_type_list.push(ref_and_type);
126 }
127 object_and_type_list
128 };
129 move_calls::identity::create_and_execute_send(
130 identity_ref,
131 controller_cap_ref,
132 action.0,
133 expiration,
134 object_type_list,
135 package,
136 )
137 } else {
138 move_calls::identity::propose_send(identity_ref, controller_cap_ref, action.0, expiration, package)
139 }
140 .map_err(|e| Error::TransactionBuildingFailed(e.to_string()))?;
141 Ok(TransactionBuilder::new(CreateProposal {
142 identity,
143 ptb: bcs::from_bytes(&tx)?,
144 chained_execution: can_execute,
145 _action: PhantomData,
146 }))
147 }
148
149 async fn into_tx<'i, C>(
150 self,
151 identity: &'i mut OnChainIdentity,
152 controller_token: &ControllerToken,
153 client: &C,
154 ) -> Result<TransactionBuilder<ExecuteProposal<'i, Self::Action>>, Error>
155 where
156 C: CoreClientReadOnly + OptionalSync,
157 {
158 if identity.id() != controller_token.controller_of() {
159 return Err(Error::Identity(format!(
160 "token {} doesn't grant access to identity {}",
161 controller_token.id(),
162 identity.id()
163 )));
164 }
165 let proposal_id = self.id();
166 let identity_ref = client
167 .get_object_ref_by_id(identity.id())
168 .await?
169 .expect("identity exists on-chain");
170 let controller_cap_ref = controller_token.controller_ref(client).await?;
171
172 let object_type_list = {
174 let ids = self.into_action().0.into_iter().map(|(obj_id, _rcp)| obj_id);
175 let mut object_and_type_list = vec![];
176 for obj_id in ids {
177 let ref_and_type = super::obj_ref_and_type_for_id(client, obj_id)
178 .await
179 .map_err(|e| Error::ObjectLookup(e.to_string()))?;
180 object_and_type_list.push(ref_and_type);
181 }
182 object_and_type_list
183 };
184 let package = identity_package_id(client).await?;
185
186 let tx =
187 move_calls::identity::execute_send(identity_ref, controller_cap_ref, proposal_id, object_type_list, package)
188 .map_err(|e| Error::TransactionBuildingFailed(e.to_string()))?;
189
190 Ok(TransactionBuilder::new(ExecuteProposal {
191 identity,
192 ptb: bcs::from_bytes(&tx)?,
193 _action: PhantomData,
194 }))
195 }
196
197 fn parse_tx_effects(_effects: &IotaTransactionBlockEffects) -> Result<Self::Output, Error> {
198 Ok(())
199 }
200}
201
202#[derive(Debug, Deserialize, Serialize)]
203struct IotaSendAction {
204 objects: Vec<ObjectID>,
205 recipients: Vec<IotaAddress>,
206}
207
208impl From<IotaSendAction> for SendAction {
209 fn from(value: IotaSendAction) -> Self {
210 let IotaSendAction { objects, recipients } = value;
211 let transfer_map = objects.into_iter().zip(recipients).collect();
212 SendAction(transfer_map)
213 }
214}
215
216impl From<SendAction> for IotaSendAction {
217 fn from(action: SendAction) -> Self {
218 let (objects, recipients) = action.0.into_iter().unzip();
219 Self { objects, recipients }
220 }
221}