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 gas_objs = self.0.get_owned_objects(signer, GasCoin::type_()).await?;
51
52 for obj in gas_objs {
53 let response = self
54 .0
55 .get_object_with_options(obj.object_id, IotaObjectDataOptions::new().with_bcs())
56 .await?;
57 let obj = response.object()?;
58 let gas: GasCoin = bcs::from_bytes(
59 &obj.bcs
60 .as_ref()
61 .ok_or_else(|| anyhow!("bcs field is unexpectedly empty"))?
62 .try_as_move()
63 .ok_or_else(|| anyhow!("Cannot parse move object to gas object"))?
64 .bcs_bytes,
65 )?;
66 if !input_objects.contains(&obj.object_id) && gas.value() >= gas_budget {
67 return Ok(obj.object_ref());
68 }
69 }
70 Err(anyhow!(
71 "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."
72 ))
73 }
74 }
75
76 pub async fn input_refs(&self, obj_ids: &[ObjectID]) -> Result<Vec<ObjectRef>, anyhow::Error> {
78 let handles: Vec<_> = obj_ids.iter().map(|id| self.get_object_ref(*id)).collect();
79 let obj_refs = join_all(handles)
80 .await
81 .into_iter()
82 .collect::<anyhow::Result<Vec<ObjectRef>>>()?;
83 Ok(obj_refs)
84 }
85
86 async fn get_object_arg(
89 &self,
90 id: ObjectID,
91 is_mutable_ref: bool,
92 view: &CompiledModule,
93 arg_type: &SignatureToken,
94 ) -> Result<ObjectArg, anyhow::Error> {
95 let response = self
96 .0
97 .get_object_with_options(id, IotaObjectDataOptions::bcs_lossless())
98 .await?;
99
100 let obj: Object = response.into_object()?.try_into()?;
101 let obj_ref = obj.compute_object_reference();
102 let owner = obj.owner;
103 if is_receiving_argument(view, arg_type) {
104 return Ok(ObjectArg::Receiving(obj_ref));
105 }
106 Ok(match owner {
107 Owner::Shared {
108 initial_shared_version,
109 } => ObjectArg::SharedObject {
110 id,
111 initial_shared_version,
112 mutable: is_mutable_ref,
113 },
114 Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable => {
115 ObjectArg::ImmOrOwnedObject(obj_ref)
116 }
117 })
118 }
119
120 pub async fn resolve_and_checks_json_args(
123 &self,
124 builder: &mut ProgrammableTransactionBuilder,
125 package_id: ObjectID,
126 module_ident: &Identifier,
127 function_ident: &Identifier,
128 type_args: &[TypeTag],
129 json_args: Vec<IotaJsonValue>,
130 ) -> Result<Vec<Argument>, anyhow::Error> {
131 let package = self.fetch_move_package(package_id).await?;
133 let module = package.deserialize_module(module_ident, &BinaryConfig::standard())?;
134
135 let json_args_and_tokens = resolve_move_function_args(
137 &package,
138 module_ident.clone(),
139 function_ident.clone(),
140 type_args,
141 json_args,
142 )?;
143
144 let mut args = Vec::new();
146 for (arg, expected_type) in json_args_and_tokens {
147 args.push(match arg {
148 ResolvedCallArg::Pure(p) => builder.input(CallArg::Pure(p)),
149 ResolvedCallArg::Object(id) => builder.input(CallArg::Object(
150 self.get_object_arg(
151 id,
152 matches!(expected_type, SignatureToken::MutableReference(_))
154 || !expected_type.is_reference(),
155 &module,
156 &expected_type,
157 )
158 .await?,
159 )),
160 ResolvedCallArg::ObjVec(v) => {
161 let mut object_ids = vec![];
162 for id in v {
163 object_ids.push(
164 self.get_object_arg(
165 id,
166 false,
168 &module,
169 &expected_type,
170 )
171 .await?,
172 )
173 }
174 builder.make_obj_vec(object_ids)
175 }
176 }?);
177 }
178
179 Ok(args)
180 }
181
182 pub async fn resolve_and_check_call_args(
185 &self,
186 builder: &mut ProgrammableTransactionBuilder,
187 package_id: ObjectID,
188 module: &Identifier,
189 function: &Identifier,
190 type_args: &[TypeTag],
191 call_args: Vec<PtbInput>,
192 ) -> Result<Vec<Argument>, anyhow::Error> {
193 let package = self.fetch_move_package(package_id).await?;
194
195 let module_compiled = package.deserialize_module(module, &BinaryConfig::standard())?;
196 let function_str = function.as_ident_str();
197 let function_def = module_compiled
198 .function_defs
199 .iter()
200 .find(|function_def| {
201 module_compiled.identifier_at(
202 module_compiled
203 .function_handle_at(function_def.function)
204 .name,
205 ) == function_str
206 })
207 .ok_or_else(|| anyhow!("Could not resolve function {function} in module {module}"))?;
208 let function_signature = module_compiled.function_handle_at(function_def.function);
209 let parameters = &module_compiled
210 .signature_at(function_signature.parameters)
211 .0;
212
213 let expected_len = match parameters.last() {
214 Some(param) if TxContext::kind(&module_compiled, param) != TxContextKind::None => {
215 parameters.len() - 1
216 }
217 _ => parameters.len(),
218 };
219
220 if call_args.len() != expected_len {
221 bail!("Expected {expected_len} args, found {}", call_args.len());
222 }
223
224 let mut arguments = Vec::with_capacity(expected_len);
225
226 for (idx, (arg, param)) in call_args
227 .into_iter()
228 .zip(parameters.iter().take(expected_len))
229 .enumerate()
230 {
231 let argument = match arg {
232 PtbInput::CallArg(value) => {
233 let json_slice = [value];
234 let param_slice = [param.clone()];
235 let resolved =
236 resolve_call_args(&module_compiled, type_args, &json_slice, ¶m_slice)?;
237 let resolved_arg = resolved
238 .into_iter()
239 .next()
240 .ok_or_else(|| anyhow!("Unable to resolve pure argument at index {idx}"))?;
241
242 match resolved_arg {
243 ResolvedCallArg::Pure(bytes) => builder.input(CallArg::Pure(bytes))?,
244 ResolvedCallArg::Object(id) => {
245 let is_mutable = match param {
246 SignatureToken::MutableReference(_) => true,
247 _ => !param.is_reference(),
248 };
249 let object_arg = self
250 .get_object_arg(id, is_mutable, &module_compiled, param)
251 .await?;
252 builder.input(CallArg::Object(object_arg))?
253 }
254 ResolvedCallArg::ObjVec(vec_ids) => {
255 let mut object_args = Vec::with_capacity(vec_ids.len());
256 for id in vec_ids {
257 object_args.push(
258 self.get_object_arg(id, false, &module_compiled, param)
259 .await?,
260 );
261 }
262 builder.make_obj_vec(object_args)?
263 }
264 }
265 }
266 PtbInput::PtbRef(iota_arg) => match iota_arg {
267 IotaArgument::GasCoin => Argument::GasCoin,
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 .to_owned(),
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 get_object_ref(&self, object_id: ObjectID) -> anyhow::Result<ObjectRef> {
352 self.get_object_ref_and_type(object_id)
354 .await
355 .map(|(oref, _)| oref)
356 }
357
358 pub(crate) async fn get_object_ref_and_type(
361 &self,
362 object_id: ObjectID,
363 ) -> anyhow::Result<(ObjectRef, ObjectType)> {
364 let object = self
365 .0
366 .get_object_with_options(object_id, IotaObjectDataOptions::new().with_type())
367 .await?
368 .into_object()?;
369
370 Ok((object.object_ref(), object.object_type()?))
371 }
372
373 async fn fetch_move_package(&self, package_id: ObjectID) -> Result<MovePackage, anyhow::Error> {
375 let object = self
376 .0
377 .get_object_with_options(package_id, IotaObjectDataOptions::bcs_lossless())
378 .await?
379 .into_object()?;
380 let Some(IotaRawData::Package(package)) = object.bcs else {
381 bail!("Bcs field in object [{package_id}] is missing or not a package.");
382 };
383 Ok(MovePackage::new(
384 package.id,
385 object.version,
386 package.module_map,
387 ProtocolConfig::get_for_min_version().max_move_package_size(),
388 package.type_origin_table,
389 package.linkage_table,
390 )?)
391 }
392}
393
394fn check_function_has_a_return(
397 module: &CompiledModule,
398 function_ident: &Identifier,
399) -> Result<(), anyhow::Error> {
400 let (_, fdef) = module
401 .find_function_def_by_name(function_ident.as_str())
402 .ok_or_else(|| {
403 anyhow!(
404 "Could not resolve function {} in module {}",
405 function_ident,
406 module.self_id()
407 )
408 })?;
409 let function_signature = module.function_handle_at(fdef.function);
410 fp_ensure!(
411 !&module.signature_at(function_signature.return_).is_empty(),
412 UserInputError::InvalidMoveViewFunction {
413 error: "No return type for this function".to_owned(),
414 }
415 .into()
416 );
417 Ok(())
418}