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