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_sdk_types::{
16 Address, Argument, Identifier, ObjectId, ObjectReference, Owner, SharedObjectReference,
17 StructTag, TypeTag, move_package::MovePackage,
18};
19use iota_types::{
20 base_types::{ObjectType, TxContext, TxContextKind},
21 error::{IotaError, UserInputError},
22 fp_ensure,
23 gas_coin::GasCoin,
24 move_package::{
25 IotaAttributeV2, MovePackageExt, ProtocolBuildConfig, RuntimeModuleMetadata,
26 RuntimeModuleMetadataWrapper,
27 },
28 object::Object,
29 programmable_transaction_builder::ProgrammableTransactionBuilder,
30 transaction::CallArg,
31};
32use move_binary_format::{
33 CompiledModule, binary_config::BinaryConfig, file_format::SignatureToken,
34 file_format_common::IOTA_METADATA_KEY,
35};
36
37use crate::TransactionBuilder;
38
39impl TransactionBuilder {
40 pub async fn select_gas(
42 &self,
43 signer: Address,
44 input_gas: impl Into<Option<ObjectId>>,
45 gas_budget: u64,
46 input_objects: Vec<ObjectId>,
47 gas_price: u64,
48 ) -> Result<ObjectReference, anyhow::Error> {
49 if gas_budget < gas_price {
50 bail!(
51 "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}."
52 )
53 }
54 if let Some(gas) = input_gas.into() {
55 self.get_object_ref(gas).await
56 } else {
57 let mut cursor = None;
58 loop {
60 let page = self
61 .0
62 .get_owned_objects(
63 signer,
64 StructTag::new_gas_coin(),
65 cursor,
66 None,
67 IotaObjectDataOptions::new().with_bcs(),
68 )
69 .await?;
70 for response in &page.data {
71 let obj = response.object()?;
72 let gas: GasCoin = bcs::from_bytes(
73 &obj.bcs
74 .as_ref()
75 .ok_or_else(|| anyhow!("bcs field is unexpectedly empty"))?
76 .try_as_move()
77 .ok_or_else(|| anyhow!("Cannot parse move object to gas object"))?
78 .bcs_bytes,
79 )?;
80 if !input_objects.contains(&obj.object_id) && gas.value() >= gas_budget {
81 return Ok(obj.object_ref());
82 }
83 }
84 if !page.has_next_page {
85 break;
86 }
87 cursor = page.next_cursor;
88 }
89
90 Err(anyhow!(
91 "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."
92 ))
93 }
94 }
95
96 pub async fn input_refs(
98 &self,
99 obj_ids: &[ObjectId],
100 ) -> Result<Vec<ObjectReference>, anyhow::Error> {
101 let handles: Vec<_> = obj_ids.iter().map(|id| self.get_object_ref(*id)).collect();
102 let obj_refs = join_all(handles)
103 .await
104 .into_iter()
105 .collect::<anyhow::Result<Vec<ObjectReference>>>()?;
106 Ok(obj_refs)
107 }
108
109 async fn get_object_arg(
112 &self,
113 id: ObjectId,
114 is_mutable_ref: bool,
115 view: &CompiledModule,
116 arg_type: &SignatureToken,
117 ) -> Result<CallArg, anyhow::Error> {
118 let response = self
119 .0
120 .get_object_with_options(id, IotaObjectDataOptions::bcs_lossless())
121 .await?;
122
123 let obj: Object = response.into_object()?.try_into()?;
124 let obj_ref = obj.object_ref();
125 let owner = obj.owner;
126 if is_receiving_argument(view, arg_type) {
127 return Ok(CallArg::Receiving(obj_ref));
128 }
129 Ok(match owner {
130 Owner::Shared(initial_shared_version) => CallArg::Shared(SharedObjectReference::new(
131 id,
132 initial_shared_version,
133 is_mutable_ref,
134 )),
135 Owner::Address(_) | Owner::Object(_) | Owner::Immutable => {
136 CallArg::ImmutableOrOwned(obj_ref)
137 }
138 _ => unimplemented!("a new Owner enum variant was added and needs to be handled"),
139 })
140 }
141
142 async fn resolved_call_arg_to_call_arg(
145 &self,
146 resolved_arg: ResolvedCallArg,
147 param: &SignatureToken,
148 module: &CompiledModule,
149 ) -> Result<ResolvedCallArgResult, anyhow::Error> {
150 match resolved_arg {
151 ResolvedCallArg::Pure(bytes) => {
152 Ok(ResolvedCallArgResult::CallArg(CallArg::Pure(bytes)))
153 }
154 ResolvedCallArg::Object(id) => {
155 let is_mutable =
156 matches!(param, SignatureToken::MutableReference(_)) || !param.is_reference();
157 let object_arg = self.get_object_arg(id, is_mutable, module, param).await?;
158 Ok(ResolvedCallArgResult::CallArg(object_arg))
159 }
160 ResolvedCallArg::ObjVec(vec_ids) => {
161 let mut object_args = Vec::with_capacity(vec_ids.len());
162 for id in vec_ids {
163 object_args.push(self.get_object_arg(id, false, module, param).await?);
164 }
165 Ok(ResolvedCallArgResult::ObjVec(object_args))
166 }
167 }
168 }
169
170 async fn resolve_json_value_to_call_arg(
172 &self,
173 module: &CompiledModule,
174 type_args: &[TypeTag],
175 value: IotaJsonValue,
176 param: &SignatureToken,
177 idx: usize,
178 ) -> Result<ResolvedCallArgResult, anyhow::Error> {
179 let json_slice = [value];
180 let param_slice = [param.clone()];
181 let resolved = resolve_call_args(module, type_args, &json_slice, ¶m_slice)?;
182 let resolved_arg = resolved
183 .into_iter()
184 .next()
185 .ok_or_else(|| anyhow!("Unable to resolve argument at index {idx}"))?;
186 self.resolved_call_arg_to_call_arg(resolved_arg, param, module)
187 .await
188 }
189
190 pub async fn resolve_and_checks_json_args(
193 &self,
194 builder: &mut ProgrammableTransactionBuilder,
195 package_id: ObjectId,
196 module_ident: &Identifier,
197 function_ident: &Identifier,
198 type_args: &[TypeTag],
199 json_args: Vec<IotaJsonValue>,
200 ) -> Result<Vec<Argument>, anyhow::Error> {
201 let package = self.fetch_move_package(package_id).await?;
203 let module = package.deserialize_module(module_ident, &BinaryConfig::standard())?;
204
205 let json_args_and_tokens = resolve_move_function_args(
207 &package,
208 module_ident.to_owned(),
209 function_ident.to_owned(),
210 type_args,
211 json_args,
212 )?;
213
214 let mut args = Vec::new();
216 for (arg, expected_type) in json_args_and_tokens {
217 let result = self
218 .resolved_call_arg_to_call_arg(arg, &expected_type, &module)
219 .await?;
220 args.push(match result {
221 ResolvedCallArgResult::CallArg(call_arg) => builder.input(call_arg)?,
222 ResolvedCallArgResult::ObjVec(object_args) => builder.make_obj_vec(object_args)?,
223 });
224 }
225
226 Ok(args)
227 }
228
229 pub async fn resolve_and_check_call_args(
232 &self,
233 builder: &mut ProgrammableTransactionBuilder,
234 package_id: ObjectId,
235 module: &Identifier,
236 function: &Identifier,
237 type_args: &[TypeTag],
238 call_args: Vec<PtbInput>,
239 ) -> Result<Vec<Argument>, anyhow::Error> {
240 let package = self.fetch_move_package(package_id).await?;
241 let module_compiled = package.deserialize_module(module, &BinaryConfig::standard())?;
242 let parameters = get_function_parameters(&module_compiled, function)?;
243 let expected_len = expected_arg_count(&module_compiled, parameters);
244
245 if call_args.len() != expected_len {
246 bail!("Expected {expected_len} args, found {}", call_args.len());
247 }
248
249 let mut arguments = Vec::with_capacity(expected_len);
250
251 for (idx, (arg, param)) in call_args
252 .into_iter()
253 .zip(parameters.iter().take(expected_len))
254 .enumerate()
255 {
256 let argument = match arg {
257 PtbInput::CallArg(value) => {
258 let resolved_arg = self
259 .resolve_json_value_to_call_arg(
260 &module_compiled,
261 type_args,
262 value,
263 param,
264 idx,
265 )
266 .await?;
267 match resolved_arg {
268 ResolvedCallArgResult::CallArg(call_arg) => builder.input(call_arg)?,
269 ResolvedCallArgResult::ObjVec(object_args) => {
270 builder.make_obj_vec(object_args)?
271 }
272 }
273 }
274 PtbInput::PtbRef(iota_arg) => match iota_arg {
275 IotaArgument::GasCoin => Argument::Gas,
276 IotaArgument::Input(idx) => Argument::Input(idx),
277 IotaArgument::Result(idx) => Argument::Result(idx),
278 IotaArgument::NestedResult(idx, nested_idx) => {
279 Argument::NestedResult(idx, nested_idx)
280 }
281 },
282 };
283
284 arguments.push(argument);
285 }
286
287 Ok(arguments)
288 }
289
290 pub async fn resolve_and_checks_json_view_args(
295 &self,
296 builder: &mut ProgrammableTransactionBuilder,
297 package_id: ObjectId,
298 module_ident: &Identifier,
299 function_ident: &Identifier,
300 type_args: &[TypeTag],
301 json_args: Vec<IotaJsonValue>,
302 ) -> Result<Vec<Argument>, anyhow::Error> {
303 let package = self.fetch_move_package(package_id).await?;
305 let module = package.deserialize_module(module_ident, &BinaryConfig::standard())?;
306
307 fp_ensure!(
308 module.find_function_def_by_name(function_ident.as_str()).is_some(),
309 UserInputError::InvalidMoveViewFunction {
310 error: format!(
311 "function {function_ident} not found in module {module_ident} of package {package_id}"
312 ),
313 }
314 .into()
315 );
316
317 let is_view = is_view_function_from_module_metadata(&module, function_ident.as_str())?;
321 fp_ensure!(
322 is_view,
323 UserInputError::InvalidMoveViewFunction {
324 error: format!(
325 "function {function_ident} in module {module_ident} of package {package_id} is not declared as a #[view] function"
326 ),
327 }
328 .into()
329 );
330
331 let json_args_and_tokens = resolve_move_function_args(
333 &package,
334 module_ident.clone(),
335 function_ident.clone(),
336 type_args,
337 json_args,
338 )?;
339
340 let mut args = Vec::new();
342 for (arg, expected_type) in json_args_and_tokens {
343 args.push(match arg {
344 ResolvedCallArg::Pure(p) => Ok(builder.pure_bytes(p, false)),
347 ResolvedCallArg::Object(id) => {
349 fp_ensure!(
350 matches!(expected_type, SignatureToken::Reference(_)),
351 UserInputError::InvalidMoveViewFunction {
352 error: format!("Found a function parameter which is not an immutable reference {expected_type:?}")
353 ,
354 }
355 .into()
356 );
357 builder.input(
358 self.get_object_arg(
359 id,
360 false,
362 &module,
363 &expected_type,
364 )
365 .await?,
366 )
367 }
368 ResolvedCallArg::ObjVec(_) => Err(UserInputError::InvalidMoveViewFunction {
370 error: "Found a function parameter which is a vector of objects".to_owned(),
371 }
372 .into()),
373 }?);
374 }
375
376 Ok(args)
377 }
378
379 pub async fn resolve_and_check_json_args_to_call_args(
385 &self,
386 package_id: ObjectId,
387 module: &Identifier,
388 function: &Identifier,
389 type_args: &[TypeTag],
390 call_args: Vec<IotaJsonValue>,
391 ) -> Result<Vec<CallArg>, anyhow::Error> {
392 let package = self.fetch_move_package(package_id).await?;
393 let module_compiled = package.deserialize_module(module, &BinaryConfig::standard())?;
394 let parameters = get_function_parameters(&module_compiled, function)?;
395 let expected_len = expected_arg_count(&module_compiled, parameters);
396
397 let mut arguments = Vec::with_capacity(expected_len);
398
399 for (idx, (value, param)) in call_args
400 .into_iter()
401 .zip(parameters.iter().take(expected_len))
402 .enumerate()
403 {
404 let resolved_arg = self
405 .resolve_json_value_to_call_arg(&module_compiled, type_args, value, param, idx)
406 .await?;
407
408 match resolved_arg {
409 ResolvedCallArgResult::CallArg(call_arg) => arguments.push(call_arg),
410 ResolvedCallArgResult::ObjVec(object_args) => {
411 for obj_arg in object_args {
413 arguments.push(obj_arg);
414 }
415 }
416 }
417 }
418
419 Ok(arguments)
420 }
421
422 pub async fn get_object_ref(&self, object_id: ObjectId) -> anyhow::Result<ObjectReference> {
424 self.get_object_ref_and_type(object_id)
426 .await
427 .map(|(oref, _)| oref)
428 }
429
430 pub(crate) async fn get_object_ref_and_type(
434 &self,
435 object_id: ObjectId,
436 ) -> anyhow::Result<(ObjectReference, ObjectType)> {
437 let object = self
438 .0
439 .get_object_with_options(object_id, IotaObjectDataOptions::new().with_type())
440 .await?
441 .into_object()?;
442
443 Ok((object.object_ref(), object.object_type()?))
444 }
445
446 async fn fetch_move_package(&self, package_id: ObjectId) -> Result<MovePackage, anyhow::Error> {
448 let object = self
449 .0
450 .get_object_with_options(package_id, IotaObjectDataOptions::bcs_lossless())
451 .await?
452 .into_object()?;
453 let Some(IotaRawData::Package(package)) = object.bcs else {
454 bail!("Bcs field in object [{package_id}] is missing or not a package.");
455 };
456
457 Ok(MovePackage::new(
458 package.id,
459 object.version,
460 package
461 .module_map
462 .iter()
463 .map(|(k, v)| (Identifier::new_unchecked(k.as_str()), v.clone()))
464 .collect(),
465 ProtocolConfig::get_for_min_version().max_move_package_size(),
466 package.type_origin_table,
467 package
468 .linkage_table
469 .into_iter()
470 .map(|(k, v)| (k, v.into()))
471 .collect(),
472 )?)
473 }
474}
475
476fn is_view_function_from_module_metadata(
483 module: &CompiledModule,
484 function_name: &str,
485) -> Result<bool, IotaError> {
486 let Some(metadata) = module
487 .metadata
488 .iter()
489 .find(|metadata| metadata.key == IOTA_METADATA_KEY)
490 else {
491 return Ok(false);
492 };
493 let metadata_wrapper: RuntimeModuleMetadataWrapper =
494 bcs::from_bytes(&metadata.value).map_err(|error| {
495 IotaError::RuntimeModuleMetadataDeserialization {
496 error: error.to_string(),
497 }
498 })?;
499 let metadata = metadata_wrapper.try_into_runtime_module_metadata(&ProtocolBuildConfig {
502 allow_view_function: true,
503 max_move_package_size: None,
504 })?;
505 Ok(match metadata {
506 RuntimeModuleMetadata::V1(_) => false,
507 RuntimeModuleMetadata::V2(metadata_v2) => metadata_v2
508 .fun_attributes
509 .get(function_name)
510 .is_some_and(|attributes| {
511 attributes
512 .iter()
513 .any(|attribute| matches!(attribute, IotaAttributeV2::View))
514 }),
515 })
516}
517
518enum ResolvedCallArgResult {
521 CallArg(CallArg),
522 ObjVec(Vec<CallArg>),
523}
524
525fn get_function_parameters<'a>(
527 module: &'a CompiledModule,
528 function: &Identifier,
529) -> Result<&'a [SignatureToken], anyhow::Error> {
530 let function_str = function.as_str();
531 let function_def = module
532 .function_defs
533 .iter()
534 .find(|function_def| {
535 module
536 .identifier_at(module.function_handle_at(function_def.function).name)
537 .as_str()
538 == function_str
539 })
540 .ok_or_else(|| {
541 anyhow!(
542 "Could not resolve function {function} in module {}",
543 module.self_id()
544 )
545 })?;
546 let function_signature = module.function_handle_at(function_def.function);
547 Ok(&module.signature_at(function_signature.parameters).0)
548}
549
550fn expected_arg_count(module: &CompiledModule, parameters: &[SignatureToken]) -> usize {
552 match parameters.last() {
553 Some(param) if TxContext::kind(module, param) != TxContextKind::None => {
554 parameters.len() - 1
555 }
556 _ => parameters.len(),
557 }
558}