1use std::result::Result;
6
7use anyhow::{anyhow, bail};
8use futures::future::join_all;
9use iota_json::{
10 IotaJsonValue, ResolvedCallArg, is_receiving_argument, resolve_call_args,
11 resolve_move_function_args,
12};
13use iota_json_rpc_types::{IotaArgument, IotaData, IotaObjectDataOptions, IotaRawData, PtbInput};
14use iota_protocol_config::ProtocolConfig;
15use iota_types::{
16 base_types::{
17 Identifier, IotaAddress, ObjectID, ObjectRef, ObjectType, StructTag, TxContext,
18 TxContextKind, TypeTag,
19 },
20 error::UserInputError,
21 fp_ensure,
22 gas_coin::GasCoin,
23 move_package::MovePackage,
24 object::{Object, Owner},
25 programmable_transaction_builder::ProgrammableTransactionBuilder,
26 transaction::{Argument, CallArg, ObjectArg},
27};
28use move_binary_format::{
29 CompiledModule, binary_config::BinaryConfig, file_format::SignatureToken,
30};
31
32use crate::TransactionBuilder;
33
34impl TransactionBuilder {
35 pub async fn select_gas(
37 &self,
38 signer: IotaAddress,
39 input_gas: impl Into<Option<ObjectID>>,
40 gas_budget: u64,
41 input_objects: Vec<ObjectID>,
42 gas_price: u64,
43 ) -> Result<ObjectRef, anyhow::Error> {
44 if gas_budget < gas_price {
45 bail!(
46 "Gas budget {gas_budget} is less than the reference gas price {gas_price}. The gas budget must be at least the current reference gas price of {gas_price}."
47 )
48 }
49 if let Some(gas) = input_gas.into() {
50 self.get_object_ref(gas).await
51 } else {
52 let mut cursor = None;
53 loop {
55 let page = self
56 .0
57 .get_owned_objects(
58 signer,
59 StructTag::new_gas_coin(),
60 cursor,
61 None,
62 IotaObjectDataOptions::new().with_bcs(),
63 )
64 .await?;
65 for response in &page.data {
66 let obj = response.object()?;
67 let gas: GasCoin = bcs::from_bytes(
68 &obj.bcs
69 .as_ref()
70 .ok_or_else(|| anyhow!("bcs field is unexpectedly empty"))?
71 .try_as_move()
72 .ok_or_else(|| anyhow!("Cannot parse move object to gas object"))?
73 .bcs_bytes,
74 )?;
75 if !input_objects.contains(&obj.object_id) && gas.value() >= gas_budget {
76 return Ok(obj.object_ref());
77 }
78 }
79 if !page.has_next_page {
80 break;
81 }
82 cursor = page.next_cursor;
83 }
84
85 Err(anyhow!(
86 "Cannot find gas coin for signer address {signer} with amount sufficient for the required gas budget {gas_budget}. If you are using the pay or transfer commands, you can use the pay-iota command instead, which will use the only object as gas payment."
87 ))
88 }
89 }
90
91 pub async fn input_refs(&self, obj_ids: &[ObjectID]) -> Result<Vec<ObjectRef>, anyhow::Error> {
93 let handles: Vec<_> = obj_ids.iter().map(|id| self.get_object_ref(*id)).collect();
94 let obj_refs = join_all(handles)
95 .await
96 .into_iter()
97 .collect::<anyhow::Result<Vec<ObjectRef>>>()?;
98 Ok(obj_refs)
99 }
100
101 async fn get_object_arg(
104 &self,
105 id: ObjectID,
106 is_mutable_ref: bool,
107 view: &CompiledModule,
108 arg_type: &SignatureToken,
109 ) -> Result<ObjectArg, anyhow::Error> {
110 let response = self
111 .0
112 .get_object_with_options(id, IotaObjectDataOptions::bcs_lossless())
113 .await?;
114
115 let obj: Object = response.into_object()?.try_into()?;
116 let obj_ref = obj.compute_object_reference();
117 let owner = obj.owner;
118 if is_receiving_argument(view, arg_type) {
119 return Ok(ObjectArg::Receiving(obj_ref));
120 }
121 Ok(match owner {
122 Owner::Shared(initial_shared_version) => ObjectArg::SharedObject {
123 id,
124 initial_shared_version,
125 mutable: is_mutable_ref,
126 },
127 Owner::Address(_) | Owner::Object(_) | Owner::Immutable => {
128 ObjectArg::ImmOrOwnedObject(obj_ref)
129 }
130 _ => unimplemented!("a new Owner enum variant was added and needs to be handled"),
131 })
132 }
133
134 async fn resolved_call_arg_to_call_arg(
137 &self,
138 resolved_arg: ResolvedCallArg,
139 param: &SignatureToken,
140 module: &CompiledModule,
141 ) -> Result<ResolvedCallArgResult, anyhow::Error> {
142 match resolved_arg {
143 ResolvedCallArg::Pure(bytes) => {
144 Ok(ResolvedCallArgResult::CallArg(CallArg::Pure(bytes)))
145 }
146 ResolvedCallArg::Object(id) => {
147 let is_mutable =
148 matches!(param, SignatureToken::MutableReference(_)) || !param.is_reference();
149 let object_arg = self.get_object_arg(id, is_mutable, module, param).await?;
150 Ok(ResolvedCallArgResult::CallArg(CallArg::Object(object_arg)))
151 }
152 ResolvedCallArg::ObjVec(vec_ids) => {
153 let mut object_args = Vec::with_capacity(vec_ids.len());
154 for id in vec_ids {
155 object_args.push(self.get_object_arg(id, false, module, param).await?);
156 }
157 Ok(ResolvedCallArgResult::ObjVec(object_args))
158 }
159 }
160 }
161
162 async fn resolve_json_value_to_call_arg(
164 &self,
165 module: &CompiledModule,
166 type_args: &[TypeTag],
167 value: IotaJsonValue,
168 param: &SignatureToken,
169 idx: usize,
170 ) -> Result<ResolvedCallArgResult, anyhow::Error> {
171 let json_slice = [value];
172 let param_slice = [param.clone()];
173 let resolved = resolve_call_args(module, type_args, &json_slice, ¶m_slice)?;
174 let resolved_arg = resolved
175 .into_iter()
176 .next()
177 .ok_or_else(|| anyhow!("Unable to resolve argument at index {idx}"))?;
178 self.resolved_call_arg_to_call_arg(resolved_arg, param, module)
179 .await
180 }
181
182 pub async fn resolve_and_checks_json_args(
185 &self,
186 builder: &mut ProgrammableTransactionBuilder,
187 package_id: ObjectID,
188 module_ident: &Identifier,
189 function_ident: &Identifier,
190 type_args: &[TypeTag],
191 json_args: Vec<IotaJsonValue>,
192 ) -> Result<Vec<Argument>, anyhow::Error> {
193 let package = self.fetch_move_package(package_id).await?;
195 let module = package.deserialize_module(module_ident, &BinaryConfig::standard())?;
196
197 let json_args_and_tokens = resolve_move_function_args(
199 &package,
200 module_ident.to_owned(),
201 function_ident.to_owned(),
202 type_args,
203 json_args,
204 )?;
205
206 let mut args = Vec::new();
208 for (arg, expected_type) in json_args_and_tokens {
209 let result = self
210 .resolved_call_arg_to_call_arg(arg, &expected_type, &module)
211 .await?;
212 args.push(match result {
213 ResolvedCallArgResult::CallArg(call_arg) => builder.input(call_arg)?,
214 ResolvedCallArgResult::ObjVec(object_args) => builder.make_obj_vec(object_args)?,
215 });
216 }
217
218 Ok(args)
219 }
220
221 pub async fn resolve_and_check_call_args(
224 &self,
225 builder: &mut ProgrammableTransactionBuilder,
226 package_id: ObjectID,
227 module: &Identifier,
228 function: &Identifier,
229 type_args: &[TypeTag],
230 call_args: Vec<PtbInput>,
231 ) -> Result<Vec<Argument>, anyhow::Error> {
232 let package = self.fetch_move_package(package_id).await?;
233 let module_compiled = package.deserialize_module(module, &BinaryConfig::standard())?;
234 let parameters = get_function_parameters(&module_compiled, function)?;
235 let expected_len = expected_arg_count(&module_compiled, parameters);
236
237 if call_args.len() != expected_len {
238 bail!("Expected {expected_len} args, found {}", call_args.len());
239 }
240
241 let mut arguments = Vec::with_capacity(expected_len);
242
243 for (idx, (arg, param)) in call_args
244 .into_iter()
245 .zip(parameters.iter().take(expected_len))
246 .enumerate()
247 {
248 let argument = match arg {
249 PtbInput::CallArg(value) => {
250 let resolved_arg = self
251 .resolve_json_value_to_call_arg(
252 &module_compiled,
253 type_args,
254 value,
255 param,
256 idx,
257 )
258 .await?;
259 match resolved_arg {
260 ResolvedCallArgResult::CallArg(call_arg) => builder.input(call_arg)?,
261 ResolvedCallArgResult::ObjVec(object_args) => {
262 builder.make_obj_vec(object_args)?
263 }
264 }
265 }
266 PtbInput::PtbRef(iota_arg) => match iota_arg {
267 IotaArgument::GasCoin => Argument::Gas,
268 IotaArgument::Input(idx) => Argument::Input(idx),
269 IotaArgument::Result(idx) => Argument::Result(idx),
270 IotaArgument::NestedResult(idx, nested_idx) => {
271 Argument::NestedResult(idx, nested_idx)
272 }
273 },
274 };
275
276 arguments.push(argument);
277 }
278
279 Ok(arguments)
280 }
281
282 pub async fn resolve_and_checks_json_view_args(
287 &self,
288 builder: &mut ProgrammableTransactionBuilder,
289 package_id: ObjectID,
290 module_ident: &Identifier,
291 function_ident: &Identifier,
292 type_args: &[TypeTag],
293 json_args: Vec<IotaJsonValue>,
294 ) -> Result<Vec<Argument>, anyhow::Error> {
295 let package = self.fetch_move_package(package_id).await?;
297 let module = package.deserialize_module(module_ident, &BinaryConfig::standard())?;
298
299 check_function_has_a_return(&module, function_ident)?;
302
303 let json_args_and_tokens = resolve_move_function_args(
305 &package,
306 module_ident.clone(),
307 function_ident.clone(),
308 type_args,
309 json_args,
310 )?;
311
312 let mut args = Vec::new();
314 for (arg, expected_type) in json_args_and_tokens {
315 args.push(match arg {
316 ResolvedCallArg::Pure(p) => builder.input(CallArg::Pure(p)),
318 ResolvedCallArg::Object(id) => {
320 fp_ensure!(
321 matches!(expected_type, SignatureToken::Reference(_)),
322 UserInputError::InvalidMoveViewFunction {
323 error: format!("Found a function parameter which is not an immutable reference {expected_type:?}")
324 ,
325 }
326 .into()
327 );
328 builder.input(CallArg::Object(
329 self.get_object_arg(
330 id,
331 false,
333 &module,
334 &expected_type,
335 )
336 .await?,
337 ))
338 }
339 ResolvedCallArg::ObjVec(_) => Err(UserInputError::InvalidMoveViewFunction {
341 error: "Found a function parameter which is a vector of objects".to_owned(),
342 }
343 .into()),
344 }?);
345 }
346
347 Ok(args)
348 }
349
350 pub async fn resolve_and_check_json_args_to_call_args(
356 &self,
357 package_id: ObjectID,
358 module: &Identifier,
359 function: &Identifier,
360 type_args: &[TypeTag],
361 call_args: Vec<IotaJsonValue>,
362 ) -> Result<Vec<CallArg>, anyhow::Error> {
363 let package = self.fetch_move_package(package_id).await?;
364 let module_compiled = package.deserialize_module(module, &BinaryConfig::standard())?;
365 let parameters = get_function_parameters(&module_compiled, function)?;
366 let expected_len = expected_arg_count(&module_compiled, parameters);
367
368 let mut arguments = Vec::with_capacity(expected_len);
369
370 for (idx, (value, param)) in call_args
371 .into_iter()
372 .zip(parameters.iter().take(expected_len))
373 .enumerate()
374 {
375 let resolved_arg = self
376 .resolve_json_value_to_call_arg(&module_compiled, type_args, value, param, idx)
377 .await?;
378
379 match resolved_arg {
380 ResolvedCallArgResult::CallArg(call_arg) => arguments.push(call_arg),
381 ResolvedCallArgResult::ObjVec(object_args) => {
382 for obj_arg in object_args {
384 arguments.push(CallArg::Object(obj_arg));
385 }
386 }
387 }
388 }
389
390 Ok(arguments)
391 }
392
393 pub async fn get_object_ref(&self, object_id: ObjectID) -> anyhow::Result<ObjectRef> {
395 self.get_object_ref_and_type(object_id)
397 .await
398 .map(|(oref, _)| oref)
399 }
400
401 pub(crate) async fn get_object_ref_and_type(
404 &self,
405 object_id: ObjectID,
406 ) -> anyhow::Result<(ObjectRef, ObjectType)> {
407 let object = self
408 .0
409 .get_object_with_options(object_id, IotaObjectDataOptions::new().with_type())
410 .await?
411 .into_object()?;
412
413 Ok((object.object_ref(), object.object_type()?))
414 }
415
416 async fn fetch_move_package(&self, package_id: ObjectID) -> Result<MovePackage, anyhow::Error> {
418 let object = self
419 .0
420 .get_object_with_options(package_id, IotaObjectDataOptions::bcs_lossless())
421 .await?
422 .into_object()?;
423 let Some(IotaRawData::Package(package)) = object.bcs else {
424 bail!("Bcs field in object [{package_id}] is missing or not a package.");
425 };
426 Ok(MovePackage::new(
427 package.id,
428 object.version,
429 package.module_map,
430 ProtocolConfig::get_for_min_version().max_move_package_size(),
431 package.type_origin_table,
432 package.linkage_table,
433 )?)
434 }
435}
436
437fn check_function_has_a_return(
440 module: &CompiledModule,
441 function_ident: &Identifier,
442) -> Result<(), anyhow::Error> {
443 let (_, fdef) = module
444 .find_function_def_by_name(function_ident.as_str())
445 .ok_or_else(|| {
446 anyhow!(
447 "Could not resolve function {} in module {}",
448 function_ident,
449 module.self_id()
450 )
451 })?;
452 let function_signature = module.function_handle_at(fdef.function);
453 fp_ensure!(
454 !&module.signature_at(function_signature.return_).is_empty(),
455 UserInputError::InvalidMoveViewFunction {
456 error: "No return type for this function".to_owned(),
457 }
458 .into()
459 );
460 Ok(())
461}
462
463enum ResolvedCallArgResult {
466 CallArg(CallArg),
467 ObjVec(Vec<ObjectArg>),
468}
469
470fn get_function_parameters<'a>(
472 module: &'a CompiledModule,
473 function: &Identifier,
474) -> Result<&'a [SignatureToken], anyhow::Error> {
475 let function_str = function.as_str();
476 let function_def = module
477 .function_defs
478 .iter()
479 .find(|function_def| {
480 module
481 .identifier_at(module.function_handle_at(function_def.function).name)
482 .as_str()
483 == function_str
484 })
485 .ok_or_else(|| {
486 anyhow!(
487 "Could not resolve function {function} in module {}",
488 module.self_id()
489 )
490 })?;
491 let function_signature = module.function_handle_at(function_def.function);
492 Ok(&module.signature_at(function_signature.parameters).0)
493}
494
495fn expected_arg_count(module: &CompiledModule, parameters: &[SignatureToken]) -> usize {
497 match parameters.last() {
498 Some(param) if TxContext::kind(module, param) != TxContextKind::None => {
499 parameters.len() - 1
500 }
501 _ => parameters.len(),
502 }
503}