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