identity_iota_core/rebased/proposals/
borrow.rs1use std::collections::HashMap;
5use std::marker::PhantomData;
6
7use crate::rebased::iota::move_calls;
8use crate::rebased::iota::package::identity_package_id;
9
10use crate::rebased::migration::ControllerToken;
11
12use iota_interaction::OptionalSend;
13use product_common::core_client::CoreClientReadOnly;
14use product_common::transaction::transaction_builder::Transaction;
15use product_common::transaction::transaction_builder::TransactionBuilder;
16use product_common::transaction::ProtoTransaction;
17use serde::Deserialize;
18use tokio::sync::Mutex;
19
20use crate::rebased::migration::Proposal;
21use crate::rebased::Error;
22use async_trait::async_trait;
23use iota_interaction::rpc_types::IotaExecutionStatus;
24use iota_interaction::rpc_types::IotaObjectData;
25use iota_interaction::rpc_types::IotaTransactionBlockEffects;
26use iota_interaction::rpc_types::IotaTransactionBlockEffectsAPI as _;
27use iota_interaction::types::base_types::ObjectID;
28use iota_interaction::types::transaction::Argument;
29use iota_interaction::types::transaction::ProgrammableTransaction;
30use iota_interaction::types::TypeTag;
31use iota_interaction::MoveType;
32use iota_interaction::OptionalSync;
33use serde::Serialize;
34
35use super::CreateProposal;
36use super::OnChainIdentity;
37use super::ProposalBuilder;
38use super::ProposalT;
39use super::UserDrivenTx;
40
41cfg_if::cfg_if! {
42 if #[cfg(target_arch = "wasm32")] {
43 use iota_interaction::types::programmable_transaction_builder::ProgrammableTransactionBuilder as Ptb;
44 pub trait BorrowIntentFnT: FnOnce(&mut Ptb, &HashMap<ObjectID, (Argument, IotaObjectData)>) {}
47 impl<T> BorrowIntentFnT for T where T: FnOnce(&mut Ptb, &HashMap<ObjectID, (Argument, IotaObjectData)>) {}
48 #[allow(unreachable_pub)]
50 pub type BorrowIntentFn = Box<dyn BorrowIntentFnT>;
51 } else {
52 use iota_interaction::types::programmable_transaction_builder::ProgrammableTransactionBuilder as Ptb;
53 pub trait BorrowIntentFnT: FnOnce(&mut Ptb, &HashMap<ObjectID, (Argument, IotaObjectData)>) {}
56 impl<T> BorrowIntentFnT for T where T: FnOnce(&mut Ptb, &HashMap<ObjectID, (Argument, IotaObjectData)>) {}
57 #[allow(unreachable_pub)]
59 pub type BorrowIntentFn = Box<dyn BorrowIntentFnT + Send>;
60 }
61}
62
63#[derive(Deserialize, Serialize)]
65pub struct BorrowAction<F = BorrowIntentFn> {
66 objects: Vec<ObjectID>,
67 #[serde(skip, default = "Mutex::default")]
68 intent_fn: Mutex<Option<F>>,
69}
70
71impl<F> Default for BorrowAction<F> {
72 fn default() -> Self {
73 BorrowAction {
74 objects: vec![],
75 intent_fn: Mutex::new(None),
76 }
77 }
78}
79
80pub struct BorrowActionWithIntent<F>(BorrowAction<F>)
83where
84 F: BorrowIntentFnT;
85
86impl MoveType for BorrowAction {
87 fn move_type(package: ObjectID) -> TypeTag {
88 use std::str::FromStr;
89
90 TypeTag::from_str(&format!("{package}::borrow_proposal::Borrow")).expect("valid move type")
91 }
92}
93
94impl<F> BorrowAction<F> {
95 pub fn new<I>(objects: I) -> Self
97 where
98 I: IntoIterator<Item = ObjectID>,
99 {
100 Self {
101 objects: objects.into_iter().collect(),
102 intent_fn: Mutex::new(None),
103 }
104 }
105
106 pub fn new_with_intent<I>(objects: I, intent: F) -> Self
108 where
109 I: IntoIterator<Item = ObjectID>,
110 {
111 Self {
112 objects: objects.into_iter().collect(),
113 intent_fn: Mutex::new(Some(intent)),
114 }
115 }
116
117 pub fn objects(&self) -> &[ObjectID] {
119 &self.objects
120 }
121
122 pub fn borrow_object(&mut self, object_id: ObjectID) {
125 self.objects.push(object_id);
126 }
127
128 pub fn borrow_objects<I>(&mut self, objects: I)
130 where
131 I: IntoIterator<Item = ObjectID>,
132 {
133 objects.into_iter().for_each(|obj_id| self.borrow_object(obj_id));
134 }
135
136 async fn take_intent(&self) -> Option<F> {
137 self.intent_fn.lock().await.take()
138 }
139
140 fn plug_intent<I>(self, intent_fn: I) -> BorrowActionWithIntent<I>
141 where
142 I: BorrowIntentFnT,
143 {
144 let action = BorrowAction {
145 objects: self.objects,
146 intent_fn: Mutex::new(Some(intent_fn)),
147 };
148 BorrowActionWithIntent(action)
149 }
150}
151
152impl<'i, 'c, F> ProposalBuilder<'i, 'c, BorrowAction<F>> {
153 pub fn borrow(mut self, object_id: ObjectID) -> Self {
155 self.action.borrow_object(object_id);
156 self
157 }
158 pub fn borrow_objects<I>(self, objects: I) -> Self
160 where
161 I: IntoIterator<Item = ObjectID>,
162 {
163 objects.into_iter().fold(self, |builder, obj| builder.borrow(obj))
164 }
165
166 pub fn with_intent<F1>(self, intent_fn: F1) -> ProposalBuilder<'i, 'c, BorrowAction<F1>>
169 where
170 F1: FnOnce(&mut Ptb, &HashMap<ObjectID, (Argument, IotaObjectData)>),
171 {
172 let ProposalBuilder {
173 identity,
174 expiration,
175 controller_token,
176 action: BorrowAction { objects, .. },
177 } = self;
178 let intent_fn = Mutex::new(Some(intent_fn));
179 ProposalBuilder {
180 identity,
181 expiration,
182 controller_token,
183 action: BorrowAction { objects, intent_fn },
184 }
185 }
186}
187
188#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
189#[cfg_attr(feature = "send-sync", async_trait)]
190impl<F> ProposalT for Proposal<BorrowAction<F>>
191where
192 F: BorrowIntentFnT + OptionalSend + OptionalSync,
193{
194 type Action = BorrowAction<F>;
195 type Output = ();
196
197 async fn create<'i, C>(
198 action: Self::Action,
199 expiration: Option<u64>,
200 identity: &'i mut OnChainIdentity,
201 controller_token: &ControllerToken,
202 client: &C,
203 ) -> Result<TransactionBuilder<CreateProposal<'i, Self::Action>>, Error>
204 where
205 C: CoreClientReadOnly + OptionalSync,
206 {
207 if identity.id() != controller_token.controller_of() {
208 return Err(Error::Identity(format!(
209 "token {} doesn't grant access to identity {}",
210 controller_token.id(),
211 identity.id()
212 )));
213 }
214
215 let package = identity_package_id(client).await?;
216 let identity_ref = client
217 .get_object_ref_by_id(identity.id())
218 .await?
219 .expect("identity exists on-chain");
220 let controller_cap_ref = controller_token.controller_ref(client).await?;
221 let can_execute = identity
222 .controller_voting_power(controller_token.controller_id())
223 .expect("is a controller of identity")
224 >= identity.threshold();
225 let maybe_intent_fn = action.intent_fn.into_inner();
226 let chained_execution = can_execute && maybe_intent_fn.is_some();
227 let tx = if chained_execution {
228 let object_data_list = {
230 let mut object_data_list = vec![];
231 for obj_id in action.objects {
232 let object_data = super::obj_data_for_id(client, obj_id)
233 .await
234 .map_err(|e| Error::TransactionBuildingFailed(e.to_string()))?;
235 object_data_list.push(object_data);
236 }
237 object_data_list
238 };
239 move_calls::identity::create_and_execute_borrow(
240 identity_ref,
241 controller_cap_ref,
242 object_data_list,
243 maybe_intent_fn.unwrap(),
244 expiration,
245 package,
246 )
247 } else {
248 move_calls::identity::propose_borrow(identity_ref, controller_cap_ref, action.objects, expiration, package)
249 }
250 .map_err(|e| Error::TransactionBuildingFailed(e.to_string()))?;
251
252 Ok(TransactionBuilder::new(CreateProposal {
253 identity,
254 ptb: bcs::from_bytes(&tx)?,
255 chained_execution,
256 _action: PhantomData,
257 }))
258 }
259
260 async fn into_tx<'i, C>(
261 self,
262 identity: &'i mut OnChainIdentity,
263 controller_token: &ControllerToken,
264 _client: &C,
265 ) -> Result<UserDrivenTx<'i, Self::Action>, Error> {
266 if identity.id() != controller_token.controller_of() {
267 return Err(Error::Identity(format!(
268 "token {} doesn't grant access to identity {}",
269 controller_token.id(),
270 identity.id()
271 )));
272 }
273
274 let proposal_id = self.id();
275 let borrow_action = self.into_action();
276
277 Ok(UserDrivenTx::new(
278 identity,
279 controller_token.id(),
280 borrow_action,
281 proposal_id,
282 ))
283 }
284
285 fn parse_tx_effects(effects: &IotaTransactionBlockEffects) -> Result<Self::Output, Error> {
286 if let IotaExecutionStatus::Failure { error } = effects.status() {
287 return Err(Error::TransactionUnexpectedResponse(error.clone()));
288 }
289
290 Ok(())
291 }
292}
293
294impl<'i, F> UserDrivenTx<'i, BorrowAction<F>> {
295 pub fn with_intent<F1>(self, intent_fn: F1) -> UserDrivenTx<'i, BorrowActionWithIntent<F1>>
297 where
298 F1: BorrowIntentFnT,
299 {
300 UserDrivenTx::new(
301 self.identity,
302 self.controller_token,
303 self.action.plug_intent(intent_fn),
304 self.proposal_id,
305 )
306 }
307}
308
309impl<'i, F> ProtoTransaction for UserDrivenTx<'i, BorrowAction<F>> {
310 type Input = BorrowIntentFn;
311 type Tx = TransactionBuilder<UserDrivenTx<'i, BorrowActionWithIntent<BorrowIntentFn>>>;
312
313 fn with(self, input: Self::Input) -> Self::Tx {
314 TransactionBuilder::new(self.with_intent(input))
315 }
316}
317
318impl<F> UserDrivenTx<'_, BorrowActionWithIntent<F>>
319where
320 F: BorrowIntentFnT,
321{
322 async fn make_ptb(
323 &self,
324 client: &(impl CoreClientReadOnly + OptionalSync),
325 ) -> Result<ProgrammableTransaction, Error> {
326 let Self {
327 identity,
328 action: borrow_action,
329 proposal_id,
330 controller_token,
331 ..
332 } = self;
333 let identity_ref = client
334 .get_object_ref_by_id(identity.id())
335 .await?
336 .expect("identity exists on-chain");
337 let controller_token = client.get_object_by_id::<ControllerToken>(*controller_token).await?;
338 let controller_token_ref = controller_token.controller_ref(client).await?;
339
340 let object_data_list = {
342 let mut object_data_list = vec![];
343 for obj_id in borrow_action.0.objects.iter() {
344 let object_data = super::obj_data_for_id(client, *obj_id)
345 .await
346 .map_err(|e| Error::TransactionBuildingFailed(e.to_string()))?;
347 object_data_list.push(object_data);
348 }
349 object_data_list
350 };
351 let package = identity_package_id(client).await?;
352 let tx = move_calls::identity::execute_borrow(
353 identity_ref,
354 controller_token_ref,
355 *proposal_id,
356 object_data_list,
357 borrow_action
358 .0
359 .take_intent()
360 .await
361 .expect("BorrowActionWithIntent makes sure intent_fn is there"),
362 package,
363 )
364 .map_err(|e| Error::TransactionBuildingFailed(e.to_string()))?;
365
366 Ok(bcs::from_bytes(&tx)?)
367 }
368}
369
370#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
371#[cfg_attr(feature = "send-sync", async_trait)]
372impl<F> Transaction for UserDrivenTx<'_, BorrowActionWithIntent<F>>
373where
374 F: BorrowIntentFnT + OptionalSend,
375{
376 type Output = ();
377 type Error = Error;
378
379 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
380 where
381 C: CoreClientReadOnly + OptionalSync,
382 {
383 self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
384 }
385
386 async fn apply<C>(self, effects: &mut IotaTransactionBlockEffects, _client: &C) -> Result<Self::Output, Self::Error>
387 where
388 C: CoreClientReadOnly + OptionalSync,
389 {
390 if let IotaExecutionStatus::Failure { error } = effects.status() {
391 return Err(Error::TransactionUnexpectedResponse(error.clone()));
392 }
393
394 Ok(())
395 }
396}