iota_transaction_builder/
utils.rs1use std::{collections::BTreeMap, result::Result};
6
7use anyhow::{Ok, anyhow, bail};
8use futures::future::join_all;
9use iota_json::{
10 IotaJsonValue, ResolvedCallArg, is_receiving_argument, resolve_move_function_args,
11};
12use iota_json_rpc_types::{IotaData, IotaObjectDataOptions, IotaRawData};
13use iota_protocol_config::ProtocolConfig;
14use iota_types::{
15 base_types::{IotaAddress, ObjectID, ObjectRef, ObjectType},
16 gas_coin::GasCoin,
17 move_package::MovePackage,
18 object::{Object, Owner},
19 programmable_transaction_builder::ProgrammableTransactionBuilder,
20 transaction::{Argument, CallArg, ObjectArg},
21};
22use move_binary_format::{
23 CompiledModule, binary_config::BinaryConfig, file_format::SignatureToken,
24};
25use move_core_types::{identifier::Identifier, language_storage::TypeTag};
26
27use crate::TransactionBuilder;
28
29impl TransactionBuilder {
30 pub(crate) async fn select_gas(
32 &self,
33 signer: IotaAddress,
34 input_gas: impl Into<Option<ObjectID>>,
35 gas_budget: u64,
36 input_objects: Vec<ObjectID>,
37 gas_price: u64,
38 ) -> Result<ObjectRef, anyhow::Error> {
39 if gas_budget < gas_price {
40 bail!(
41 "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}."
42 )
43 }
44 if let Some(gas) = input_gas.into() {
45 self.get_object_ref(gas).await
46 } else {
47 let gas_objs = self.0.get_owned_objects(signer, GasCoin::type_()).await?;
48
49 for obj in gas_objs {
50 let response = self
51 .0
52 .get_object_with_options(obj.object_id, IotaObjectDataOptions::new().with_bcs())
53 .await?;
54 let obj = response.object()?;
55 let gas: GasCoin = bcs::from_bytes(
56 &obj.bcs
57 .as_ref()
58 .ok_or_else(|| anyhow!("bcs field is unexpectedly empty"))?
59 .try_as_move()
60 .ok_or_else(|| anyhow!("Cannot parse move object to gas object"))?
61 .bcs_bytes,
62 )?;
63 if !input_objects.contains(&obj.object_id) && gas.value() >= gas_budget {
64 return Ok(obj.object_ref());
65 }
66 }
67 Err(anyhow!(
68 "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."
69 ))
70 }
71 }
72
73 pub async fn input_refs(&self, obj_ids: &[ObjectID]) -> Result<Vec<ObjectRef>, anyhow::Error> {
75 let handles: Vec<_> = obj_ids.iter().map(|id| self.get_object_ref(*id)).collect();
76 let obj_refs = join_all(handles)
77 .await
78 .into_iter()
79 .collect::<anyhow::Result<Vec<ObjectRef>>>()?;
80 Ok(obj_refs)
81 }
82
83 async fn get_object_arg(
86 &self,
87 id: ObjectID,
88 objects: &mut BTreeMap<ObjectID, Object>,
89 is_mutable_ref: bool,
90 view: &CompiledModule,
91 arg_type: &SignatureToken,
92 ) -> Result<ObjectArg, anyhow::Error> {
93 let response = self
94 .0
95 .get_object_with_options(id, IotaObjectDataOptions::bcs_lossless())
96 .await?;
97
98 let obj: Object = response.into_object()?.try_into()?;
99 let obj_ref = obj.compute_object_reference();
100 let owner = obj.owner;
101 objects.insert(id, obj);
102 if is_receiving_argument(view, arg_type) {
103 return Ok(ObjectArg::Receiving(obj_ref));
104 }
105 Ok(match owner {
106 Owner::Shared {
107 initial_shared_version,
108 } => ObjectArg::SharedObject {
109 id,
110 initial_shared_version,
111 mutable: is_mutable_ref,
112 },
113 Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable => {
114 ObjectArg::ImmOrOwnedObject(obj_ref)
115 }
116 })
117 }
118
119 pub async fn resolve_and_checks_json_args(
122 &self,
123 builder: &mut ProgrammableTransactionBuilder,
124 package_id: ObjectID,
125 module: &Identifier,
126 function: &Identifier,
127 type_args: &[TypeTag],
128 json_args: Vec<IotaJsonValue>,
129 ) -> Result<Vec<Argument>, anyhow::Error> {
130 let object = self
131 .0
132 .get_object_with_options(package_id, IotaObjectDataOptions::bcs_lossless())
133 .await?
134 .into_object()?;
135 let Some(IotaRawData::Package(package)) = object.bcs else {
136 bail!(
137 "Bcs field in object [{}] is missing or not a package.",
138 package_id
139 );
140 };
141 let package: MovePackage = MovePackage::new(
142 package.id,
143 object.version,
144 package.module_map,
145 ProtocolConfig::get_for_min_version().max_move_package_size(),
146 package.type_origin_table,
147 package.linkage_table,
148 )?;
149
150 let json_args_and_tokens = resolve_move_function_args(
151 &package,
152 module.clone(),
153 function.clone(),
154 type_args,
155 json_args,
156 )?;
157
158 let mut args = Vec::new();
159 let mut objects = BTreeMap::new();
160 let module = package.deserialize_module(module, &BinaryConfig::standard())?;
161 for (arg, expected_type) in json_args_and_tokens {
162 args.push(match arg {
163 ResolvedCallArg::Pure(p) => builder.input(CallArg::Pure(p)),
164
165 ResolvedCallArg::Object(id) => builder.input(CallArg::Object(
166 self.get_object_arg(
167 id,
168 &mut objects,
169 matches!(expected_type, SignatureToken::MutableReference(_))
171 || !expected_type.is_reference(),
172 &module,
173 &expected_type,
174 )
175 .await?,
176 )),
177
178 ResolvedCallArg::ObjVec(v) => {
179 let mut object_ids = vec![];
180 for id in v {
181 object_ids.push(
182 self.get_object_arg(
183 id,
184 &mut objects,
185 false,
187 &module,
188 &expected_type,
189 )
190 .await?,
191 )
192 }
193 builder.make_obj_vec(object_ids)
194 }
195 }?);
196 }
197
198 Ok(args)
199 }
200
201 pub async fn get_object_ref(&self, object_id: ObjectID) -> anyhow::Result<ObjectRef> {
203 self.get_object_ref_and_type(object_id)
205 .await
206 .map(|(oref, _)| oref)
207 }
208
209 pub(crate) async fn get_object_ref_and_type(
212 &self,
213 object_id: ObjectID,
214 ) -> anyhow::Result<(ObjectRef, ObjectType)> {
215 let object = self
216 .0
217 .get_object_with_options(object_id, IotaObjectDataOptions::new().with_type())
218 .await?
219 .into_object()?;
220
221 Ok((object.object_ref(), object.object_type()?))
222 }
223}