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