1use std::collections::{HashMap, HashSet};
6
7use futures::future;
8use iota_move_build::CompiledPackage;
9use iota_sdk::{
10 apis::ReadApi,
11 error::Error as SdkError,
12 rpc_types::{IotaObjectDataOptions, IotaRawData, IotaRawMovePackage},
13};
14use iota_sdk_types::{Address, ObjectId};
15use move_binary_format::CompiledModule;
16use move_compiler::compiled_unit::NamedCompiledModule;
17use move_symbol_pool::Symbol;
18use toolchain::units_for_toolchain;
19
20use crate::error::{AggregateError, Error};
21
22pub mod error;
23mod toolchain;
24
25#[cfg(test)]
26mod tests;
27
28pub enum ValidationMode {
30 Deps,
32
33 Root {
35 deps: bool,
39
40 at: Option<Address>,
43 },
44}
45
46pub struct BytecodeSourceVerifier<'a> {
47 rpc_client: &'a ReadApi,
48}
49
50type LocalModules = HashMap<(Address, Symbol), (Symbol, CompiledModule)>;
52
53#[derive(Default)]
54struct OnChainRepresentation {
55 on_chain_dependencies: Option<HashSet<Address>>,
60
61 modules: HashMap<(Address, Symbol), CompiledModule>,
64}
65
66impl ValidationMode {
67 pub fn deps() -> Self {
69 Self::Deps
70 }
71
72 pub fn root() -> Self {
76 Self::Root {
77 deps: false,
78 at: None,
79 }
80 }
81
82 pub fn root_at(address: Address) -> Self {
85 Self::Root {
86 deps: false,
87 at: Some(address),
88 }
89 }
90
91 pub fn root_and_deps() -> Self {
95 Self::Root {
96 deps: true,
97 at: None,
98 }
99 }
100
101 pub fn root_and_deps_at(address: Address) -> Self {
104 Self::Root {
105 deps: true,
106 at: Some(address),
107 }
108 }
109
110 fn verify_deps(&self) -> bool {
112 matches!(self, Self::Deps | Self::Root { deps: true, .. })
113 }
114
115 fn root_address(&self, package: &CompiledPackage) -> Result<Option<Address>, Error> {
118 match self {
119 Self::Root { at: Some(addr), .. } => Ok(Some(*addr)),
120 Self::Root { at: None, .. } => Ok(Some(package.published_at.clone()?.into())),
121 Self::Deps => Ok(None),
122 }
123 }
124
125 fn on_chain_addresses(&self, package: &CompiledPackage) -> Result<Vec<Address>, Error> {
128 let mut addrs = vec![];
129
130 if let Some(addr) = self.root_address(package)? {
131 addrs.push(addr);
132 }
133
134 if self.verify_deps() {
135 addrs.extend(dependency_addresses(package));
136 }
137
138 Ok(addrs)
139 }
140
141 async fn on_chain(
144 &self,
145 package: &CompiledPackage,
146 verifier: &BytecodeSourceVerifier<'_>,
147 ) -> Result<OnChainRepresentation, AggregateError> {
148 let mut on_chain = OnChainRepresentation::default();
149 let mut errs: Vec<Error> = vec![];
150
151 let root = self.root_address(package)?;
152 let addrs = self.on_chain_addresses(package)?;
153
154 let resps =
155 future::join_all(addrs.iter().copied().map(|a| verifier.pkg_for_address(a))).await;
156
157 for (storage_id, pkg) in addrs.into_iter().zip(resps) {
158 let IotaRawMovePackage {
159 module_map,
160 linkage_table,
161 ..
162 } = pkg?;
163
164 let mut modules = module_map
165 .into_iter()
166 .map(|(name, bytes)| {
167 let Ok(module) = CompiledModule::deserialize_with_defaults(&bytes) else {
168 return Err(Error::OnChainDependencyDeserializationError {
169 address: storage_id,
170 module: name.into(),
171 });
172 };
173
174 Ok::<_, Error>((Symbol::from(name), module))
175 })
176 .peekable();
177
178 let runtime_id = match modules.peek() {
179 Some(Ok((_, module))) => *module.self_id().address(),
180
181 Some(Err(_)) => {
182 errs.push(modules.next().unwrap().unwrap_err());
186 continue;
187 }
188
189 None => {
190 errs.push(Error::EmptyOnChainPackage(storage_id));
191 continue;
192 }
193 };
194
195 for module in modules {
196 match module {
197 Ok((name, module)) => {
198 on_chain
199 .modules
200 .insert((Address::new(runtime_id.into_bytes()), name), module);
201 }
202
203 Err(e) => {
204 errs.push(e);
205 continue;
206 }
207 }
208 }
209
210 if root.is_some_and(|r| r == storage_id) {
211 on_chain.on_chain_dependencies = Some(HashSet::from_iter(
212 linkage_table
213 .into_values()
214 .map(|info| info.upgraded_id.into()),
215 ));
216 }
217 }
218
219 Ok(on_chain)
220 }
221
222 fn local(&self, package: &CompiledPackage) -> Result<LocalModules, Error> {
237 let iota_package = package;
238 let package = &package.package;
239 let root_package = package.compiled_package_info.package_name;
240 let mut map = LocalModules::new();
241
242 if self.verify_deps() {
243 let deps_compiled_units =
244 units_for_toolchain(&package.deps_compiled_units).map_err(|e| {
245 Error::CannotCheckLocalModules {
246 package: package.compiled_package_info.package_name,
247 message: e.to_string(),
248 }
249 })?;
250
251 let deps_compiled_units: Vec<_> = deps_compiled_units
253 .into_iter()
254 .filter(|pkg| iota_package.dependency_ids.published.contains_key(&pkg.0))
255 .collect();
256
257 for (package, local_unit) in deps_compiled_units {
258 let m = &local_unit.unit;
259 let module = m.name;
260 let address = Address::new(m.address.into_bytes());
261
262 if address == Address::ZERO {
265 continue;
266 }
267
268 map.insert((address, module), (package, m.module.clone()));
269 }
270
271 for (package, module) in iota_package.bytecode_deps.iter() {
273 let address = Address::new(module.address().into_bytes());
274 if address == Address::ZERO {
275 continue;
276 }
277 map.insert(
278 (address, Symbol::from(module.name().as_str())),
279 (*package, module.clone()),
280 );
281 }
282 }
283
284 let Self::Root { at, .. } = self else {
285 return Ok(map);
286 };
287
288 let root_compiled_units = units_for_toolchain(
291 &package
292 .root_compiled_units
293 .iter()
294 .map(|u| ("root".into(), u.clone()))
295 .collect(),
296 )
297 .map_err(|e| Error::CannotCheckLocalModules {
298 package: package.compiled_package_info.package_name,
299 message: e.to_string(),
300 })?;
301
302 for (_, local_unit) in root_compiled_units {
305 let m = &local_unit.unit;
306 let module = m.name;
307 let address = Address::new(m.address.into_bytes());
308
309 let (address, compiled_module) = if let Some(root_address) = at {
310 (*root_address, substitute_root_address(m, *root_address)?)
311 } else if address == Address::ZERO {
312 return Err(Error::InvalidModuleFailure {
313 name: module.to_string(),
314 message: "Can't verify unpublished source".to_string(),
315 });
316 } else {
317 (address, m.module.clone())
318 };
319
320 map.insert((address, module), (root_package, compiled_module));
321 }
322
323 if let Some(root_address) = at {
326 for (package, local_unit) in &package.deps_compiled_units {
327 let m = &local_unit.unit;
328 let module = m.name;
329 let address = Address::new(m.address.into_bytes());
330
331 if address != Address::ZERO {
332 continue;
333 }
334
335 map.insert(
336 (*root_address, module),
337 (*package, substitute_root_address(m, *root_address)?),
338 );
339 }
340 }
341
342 Ok(map)
343 }
344}
345
346impl<'a> BytecodeSourceVerifier<'a> {
347 pub fn new(rpc_client: &'a ReadApi) -> Self {
348 BytecodeSourceVerifier { rpc_client }
349 }
350
351 pub async fn verify(
355 &self,
356 package: &CompiledPackage,
357 mode: ValidationMode,
358 ) -> Result<(), AggregateError> {
359 if matches!(
360 mode,
361 ValidationMode::Root {
362 at: Some(Address::ZERO),
363 ..
364 }
365 ) {
366 return Err(Error::ZeroOnChainAddressSpecifiedFailure.into());
367 }
368
369 let local = mode.local(package)?;
370 let mut chain = mode.on_chain(package, self).await?;
371 let mut errs = vec![];
372
373 if let Some(on_chain_deps) = &mut chain.on_chain_dependencies {
377 for dependency_id in dependency_addresses(package) {
378 if dependency_id != Address::ZERO && !on_chain_deps.remove(&dependency_id) {
379 errs.push(Error::MissingDependencyInLinkageTable(dependency_id));
380 }
381 }
382 }
383
384 for on_chain_dep_id in chain.on_chain_dependencies.take().into_iter().flatten() {
385 errs.push(Error::MissingDependencyInSourcePackage(on_chain_dep_id));
386 }
387
388 for ((address, module), (package, local_module)) in local {
390 let Some(on_chain_module) = chain.modules.remove(&(address, module)) else {
391 errs.push(Error::OnChainDependencyNotFound { package, module });
392 continue;
393 };
394
395 if local_module != on_chain_module {
396 errs.push(Error::ModuleBytecodeMismatch {
397 address,
398 package,
399 module,
400 })
401 }
402 }
403
404 for (address, module) in chain.modules.into_keys() {
405 errs.push(Error::LocalDependencyNotFound { address, module });
406 }
407
408 if !errs.is_empty() {
409 return Err(AggregateError(errs));
410 }
411
412 Ok(())
413 }
414
415 async fn pkg_for_address(&self, addr: Address) -> Result<IotaRawMovePackage, Error> {
416 let obj_id = ObjectId::new(addr.into_bytes());
419
420 let obj_read = self
425 .rpc_client
426 .get_object_with_options(obj_id, IotaObjectDataOptions::new().with_bcs())
427 .await
428 .map_err(Error::DependencyObjectReadFailure)?;
429
430 let obj = obj_read
431 .into_object()
432 .map_err(Error::IotaObjectRefFailure)?
433 .bcs
434 .ok_or_else(|| {
435 Error::DependencyObjectReadFailure(SdkError::Data(
436 "Bcs field is not found".to_string(),
437 ))
438 })?;
439
440 match obj {
441 IotaRawData::Package(pkg) => Ok(pkg),
442 IotaRawData::MoveObject(move_obj) => Err(Error::ObjectFoundWhenPackageExpected(
443 Box::new((obj_id, move_obj)),
444 )),
445 }
446 }
447}
448
449fn substitute_root_address(
450 named_module: &NamedCompiledModule,
451 root: Address,
452) -> Result<CompiledModule, Error> {
453 let mut module = named_module.module.clone();
454 let address_idx = module.self_handle().address;
455
456 let Some(addr) = module.address_identifiers.get_mut(address_idx.0 as usize) else {
457 return Err(Error::InvalidModuleFailure {
458 name: named_module.name.to_string(),
459 message: "Self address field missing".into(),
460 });
461 };
462
463 if addr.as_ref() != Address::ZERO.as_bytes() {
464 return Err(Error::InvalidModuleFailure {
465 name: named_module.name.to_string(),
466 message: "Self address already populated".to_string(),
467 });
468 }
469
470 *addr = move_core_types::account_address::AccountAddress::new(root.into_bytes());
471 Ok(module)
472}
473
474fn dependency_addresses(package: &CompiledPackage) -> impl Iterator<Item = Address> + '_ {
476 package
477 .dependency_ids
478 .published
479 .values()
480 .copied()
481 .map(Into::into)
482}