From 8d96895ed5b67145d7efd9cd6c1637095e08169e Mon Sep 17 00:00:00 2001 From: Bing-Yang Lin Date: Wed, 15 Jan 2025 23:12:45 +0800 Subject: [PATCH] Add script to get all dependencies of a PR --- crates/iota-core/src/authority.rs | 10 +- crates/iota-core/src/rest_index.rs | 55 +-- .../src/handlers/checkpoint_handler.rs | 42 +- crates/iota-indexer/src/models/objects.rs | 4 +- crates/iota-indexer/src/types.rs | 6 +- crates/iota-types/src/dynamic_field.rs | 4 +- .../src/object/balance_traversal.rs | 14 +- .../iota-types/src/object/bounded_visitor.rs | 64 ++- .../move-core-types/src/annotated_value.rs | 6 +- .../move-core-types/src/annotated_visitor.rs | 194 ++++++-- .../src/unit_tests/visitor_test.rs | 465 ++++++++++++++++-- .../iota-move-natives/src/test_scenario.rs | 4 +- scripts/get_all_dependencies_of_pr.py | 117 +++++ 13 files changed, 798 insertions(+), 187 deletions(-) create mode 100644 scripts/get_all_dependencies_of_pr.py diff --git a/crates/iota-core/src/authority.rs b/crates/iota-core/src/authority.rs index 283819a9d13..9a90377466b 100644 --- a/crates/iota-core/src/authority.rs +++ b/crates/iota-core/src/authority.rs @@ -2353,6 +2353,7 @@ impl AuthorityState { let object_type = object.data.type_().unwrap().clone(); (version, digest, object_type) }; + DynamicFieldInfo { name, bcs_name, @@ -2363,15 +2364,6 @@ impl AuthorityState { digest, } } - DynamicFieldType::DynamicField { .. } => DynamicFieldInfo { - name, - bcs_name, - type_, - object_type: move_object.into_type().into_type_params()[1].to_string(), - object_id: o.id(), - version: o.version(), - digest: o.digest(), - }, })) } diff --git a/crates/iota-core/src/rest_index.rs b/crates/iota-core/src/rest_index.rs index 10c495fe29c..8dda2bb6115 100644 --- a/crates/iota-core/src/rest_index.rs +++ b/crates/iota-core/src/rest_index.rs @@ -671,45 +671,26 @@ fn try_create_dynamic_field_info( return Ok(None); } - let (name_value, dynamic_field_type, object_id) = { - let layout = iota_types::layout_resolver::into_struct_layout( - resolver - .get_annotated_layout(&move_object.type_().clone().into()) - .map_err(StorageError::custom)?, - ) - .map_err(StorageError::custom)?; - - let move_struct = move_object - .to_move_struct(&layout) - .map_err(StorageError::serialization)?; - - // SAFETY: move struct has already been validated to be of type DynamicField - DynamicFieldInfo::parse_move_object(&move_struct).unwrap() - }; - - let name_type = move_object - .type_() - .try_extract_field_name(&dynamic_field_type) - .expect("object is of type Field"); - - let name_value = name_value - .undecorate() - .simple_serialize() - .expect("serialization cannot fail"); - - let dynamic_object_id = match dynamic_field_type { - DynamicFieldType::DynamicObject => Some(object_id), - DynamicFieldType::DynamicField => None, - }; + let layout = resolver + .get_annotated_layout(&move_object.type_().clone().into()) + .map_err(StorageError::custom)? + .into_layout(); - let field_info = DynamicFieldIndexInfo { - name_type, - name_value, - dynamic_field_type, - dynamic_object_id, - }; + let field = DFV::FieldVisitor::deserialize(move_object.contents(), &layout) + .map_err(StorageError::custom)?; - Ok(Some(field_info)) + let value_metadata = field.value_metadata().map_err(StorageError::custom)?; + + Ok(Some(DynamicFieldIndexInfo { + name_type: field.name_layout.into(), + name_value: field.name_bytes.to_owned(), + dynamic_field_type: field.kind, + dynamic_object_id: if let DFV::ValueMetadata::DynamicObjectField(id) = value_metadata { + Some(id) + } else { + None + }, + })) } fn try_create_coin_index_info(object: &Object) -> Option<(CoinIndexKey, CoinIndexInfo)> { diff --git a/crates/iota-indexer/src/handlers/checkpoint_handler.rs b/crates/iota-indexer/src/handlers/checkpoint_handler.rs index 3d8489f03b9..48de813cf1a 100644 --- a/crates/iota-indexer/src/handlers/checkpoint_handler.rs +++ b/crates/iota-indexer/src/handlers/checkpoint_handler.rs @@ -556,25 +556,16 @@ where .collect(); let latest_live_output_objects = data.latest_live_output_objects(); - let latest_live_output_object_map = latest_live_output_objects - .clone() - .into_iter() - .map(|o| (o.id(), o.clone())) - .collect::>(); - let move_struct_layout_map = - get_move_struct_layout_map(latest_live_output_objects.clone(), package_resolver) - .await?; + let latest_live_output_object_layouts = + type_layout_map(&latest_live_output_objects, package_resolver).await?; let changed_objects = latest_live_output_objects .into_iter() .map(|o| { - let df_info = try_create_dynamic_field_info( - o, - &move_struct_layout_map, - &latest_live_output_object_map, - ); - df_info.map(|info| IndexedObject::from_object(checkpoint_seq, o.clone(), info)) + try_extract_df_kind(o, &latest_live_output_object_layouts) + .map(|df_kind| IndexedObject::from_object(checkpoint_seq, o.clone(), df_kind)) }) .collect::, _>>()?; + Ok(TransactionObjectChangesToCommit { changed_objects, deleted_objects: indexed_eventually_removed_objects, @@ -601,31 +592,20 @@ where }) .collect(); - let latest_live_output_objects = data.latest_live_output_objects(); - let latest_live_output_object_map = latest_live_output_objects - .clone() - .into_iter() - .map(|o| (o.id(), o.clone())) - .collect::>(); - - let output_objects = data + let output_objects: Vec<_> = data .transactions .iter() .flat_map(|tx| &tx.output_objects) - .collect::>(); + .collect(); + // TODO(gegaowp): the current df_info implementation is not correct, // but we have decided remove all df_* except df_kind. - let move_struct_layout_map = - get_move_struct_layout_map(output_objects.clone(), package_resolver).await?; + let output_layouts = type_layout_map(&output_objects, package_resolver).await?; let changed_objects = output_objects .into_iter() .map(|o| { - let df_info = try_create_dynamic_field_info( - o, - &move_struct_layout_map, - &latest_live_output_object_map, - ); - df_info.map(|info| IndexedObject::from_object(checkpoint_seq, o.clone(), info)) + try_extract_df_kind(o, &output_layouts) + .map(|df_kind| IndexedObject::from_object(checkpoint_seq, o.clone(), df_kind)) }) .collect::, _>>()?; diff --git a/crates/iota-indexer/src/models/objects.rs b/crates/iota-indexer/src/models/objects.rs index 50aeee95151..32d2597664d 100644 --- a/crates/iota-indexer/src/models/objects.rs +++ b/crates/iota-indexer/src/models/objects.rs @@ -235,7 +235,7 @@ impl From for StoredObject { let IndexedObject { checkpoint_sequence_number, object, - df_info, + df_kind, } = o; let (owner_type, owner_id) = owner_to_owner_info(&object.owner); let coin_type = object @@ -262,7 +262,7 @@ impl From for StoredObject { serialized_object: bcs::to_bytes(&object).unwrap(), coin_type, coin_balance: coin_balance.map(|b| b as i64), - df_kind: df_info.as_ref().map(|k| match k.type_ { + df_kind: df_kind.map(|k| match k { DynamicFieldType::DynamicField => 0, DynamicFieldType::DynamicObject => 1, }), diff --git a/crates/iota-indexer/src/types.rs b/crates/iota-indexer/src/types.rs index 1047811851a..f9b0045190d 100644 --- a/crates/iota-indexer/src/types.rs +++ b/crates/iota-indexer/src/types.rs @@ -334,19 +334,19 @@ pub enum DynamicFieldKind { pub struct IndexedObject { pub checkpoint_sequence_number: CheckpointSequenceNumber, pub object: Object, - pub df_info: Option, + pub df_kind: Option, } impl IndexedObject { pub fn from_object( checkpoint_sequence_number: CheckpointSequenceNumber, object: Object, - df_info: Option, + df_kind: Option, ) -> Self { Self { checkpoint_sequence_number, object, - df_info, + df_kind, } } } diff --git a/crates/iota-types/src/dynamic_field.rs b/crates/iota-types/src/dynamic_field.rs index 0b7580196d1..aea4698b38a 100644 --- a/crates/iota-types/src/dynamic_field.rs +++ b/crates/iota-types/src/dynamic_field.rs @@ -98,7 +98,9 @@ impl Display for DynamicFieldName { } } -#[derive(Clone, Serialize, Deserialize, JsonSchema, Ord, PartialOrd, Eq, PartialEq, Debug)] +#[derive( + Copy, Clone, Serialize, Deserialize, JsonSchema, Ord, PartialOrd, Eq, PartialEq, Debug, +)] pub enum DynamicFieldType { #[serde(rename_all = "camelCase")] DynamicField, diff --git a/crates/iota-types/src/object/balance_traversal.rs b/crates/iota-types/src/object/balance_traversal.rs index 3e9c9ef87c6..206e81d828e 100644 --- a/crates/iota-types/src/object/balance_traversal.rs +++ b/crates/iota-types/src/object/balance_traversal.rs @@ -5,7 +5,7 @@ use std::collections::BTreeMap; use move_core_types::{ - annotated_visitor::{self, StructDriver, Traversal}, + annotated_visitor::{self, StructDriver, Traversal, ValueDriver}, language_storage::{StructTag, TypeTag}, }; @@ -31,12 +31,12 @@ impl BalanceTraversal { } } -impl Traversal for BalanceTraversal { +impl<'b, 'l> Traversal<'b, 'l> for BalanceTraversal { type Error = annotated_visitor::Error; fn traverse_struct( &mut self, - driver: &mut StructDriver<'_, '_, '_>, + driver: &mut StructDriver<'_, 'b, 'l>, ) -> Result<(), Self::Error> { let Some(coin_type) = is_balance(&driver.struct_layout().type_) else { // Not a balance, search recursively for balances among fields. @@ -51,9 +51,13 @@ impl Traversal for BalanceTraversal { } } -impl Traversal for Accumulator { +impl<'b, 'l> Traversal<'b, 'l> for Accumulator { type Error = annotated_visitor::Error; - fn traverse_u64(&mut self, value: u64) -> Result<(), Self::Error> { + fn traverse_u64( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u64, + ) -> Result<(), Self::Error> { self.total += value; Ok(()) } diff --git a/crates/iota-types/src/object/bounded_visitor.rs b/crates/iota-types/src/object/bounded_visitor.rs index d8ccf434bf4..e72c6224c35 100644 --- a/crates/iota-types/src/object/bounded_visitor.rs +++ b/crates/iota-types/src/object/bounded_visitor.rs @@ -6,7 +6,7 @@ use anyhow::bail; use move_core_types::{ account_address::AccountAddress, annotated_value as A, - annotated_visitor::{self, StructDriver, VecDriver, Visitor}, + annotated_visitor::{self, StructDriver, ValueDriver, VecDriver, Visitor}, language_storage::TypeTag, u256::U256, }; @@ -152,49 +152,85 @@ impl BoundedVisitor { } } -impl Visitor for BoundedVisitor { +impl<'b, 'l> Visitor<'b, 'l> for BoundedVisitor { type Value = A::MoveValue; type Error = Error; - fn visit_u8(&mut self, value: u8) -> Result { + fn visit_u8( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u8, + ) -> Result { Ok(A::MoveValue::U8(value)) } - fn visit_u16(&mut self, value: u16) -> Result { + fn visit_u16( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u16, + ) -> Result { Ok(A::MoveValue::U16(value)) } - fn visit_u32(&mut self, value: u32) -> Result { + fn visit_u32( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u32, + ) -> Result { Ok(A::MoveValue::U32(value)) } - fn visit_u64(&mut self, value: u64) -> Result { + fn visit_u64( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u64, + ) -> Result { Ok(A::MoveValue::U64(value)) } - fn visit_u128(&mut self, value: u128) -> Result { + fn visit_u128( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u128, + ) -> Result { Ok(A::MoveValue::U128(value)) } - fn visit_u256(&mut self, value: U256) -> Result { + fn visit_u256( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: U256, + ) -> Result { Ok(A::MoveValue::U256(value)) } - fn visit_bool(&mut self, value: bool) -> Result { + fn visit_bool( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: bool, + ) -> Result { Ok(A::MoveValue::Bool(value)) } - fn visit_address(&mut self, value: AccountAddress) -> Result { + fn visit_address( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result { Ok(A::MoveValue::Address(value)) } - fn visit_signer(&mut self, value: AccountAddress) -> Result { + fn visit_signer( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result { Ok(A::MoveValue::Signer(value)) } fn visit_vector( &mut self, - driver: &mut VecDriver<'_, '_, '_>, + driver: &mut VecDriver<'_, 'b, 'l>, ) -> Result { let mut elems = vec![]; while let Some(elem) = driver.next_element(self)? { @@ -206,7 +242,7 @@ impl Visitor for BoundedVisitor { fn visit_struct( &mut self, - driver: &mut StructDriver<'_, '_, '_>, + driver: &mut StructDriver<'_, 'b, 'l>, ) -> Result { let tag = driver.struct_layout().type_.clone().into(); @@ -232,7 +268,7 @@ impl Visitor for BoundedVisitor { fn visit_variant( &mut self, - driver: &mut annotated_visitor::VariantDriver<'_, '_, '_>, + driver: &mut annotated_visitor::VariantDriver<'_, 'b, 'l>, ) -> Result { let type_ = driver.enum_layout().type_.clone().into(); diff --git a/external-crates/move/crates/move-core-types/src/annotated_value.rs b/external-crates/move/crates/move-core-types/src/annotated_value.rs index 8bd7df72268..c062f90ad17 100644 --- a/external-crates/move/crates/move-core-types/src/annotated_value.rs +++ b/external-crates/move/crates/move-core-types/src/annotated_value.rs @@ -17,7 +17,7 @@ use serde::{ use crate::{ account_address::AccountAddress, - annotated_visitor::{visit_struct, visit_value, Error as VError, Visitor}, + annotated_visitor::{visit_struct, visit_value, Error as VError, ValueDriver, Visitor}, identifier::Identifier, language_storage::{StructTag, TypeTag}, runtime_value::{self as R, MOVE_STRUCT_FIELDS, MOVE_STRUCT_TYPE}, @@ -79,7 +79,7 @@ pub enum MoveValue { Variant(MoveVariant), } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct MoveFieldLayout { pub name: Identifier, pub layout: MoveTypeLayout, @@ -120,7 +120,7 @@ impl MoveDatatypeLayout { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum MoveTypeLayout { #[serde(rename(serialize = "bool", deserialize = "bool"))] Bool, diff --git a/external-crates/move/crates/move-core-types/src/annotated_visitor.rs b/external-crates/move/crates/move-core-types/src/annotated_visitor.rs index 432b38722cc..1431defad51 100644 --- a/external-crates/move/crates/move-core-types/src/annotated_visitor.rs +++ b/external-crates/move/crates/move-core-types/src/annotated_visitor.rs @@ -32,29 +32,73 @@ pub trait Visitor { /// ``` type Error: From; - fn visit_u8(&mut self, value: u8) -> Result; - fn visit_u16(&mut self, value: u16) -> Result; - fn visit_u32(&mut self, value: u32) -> Result; - fn visit_u64(&mut self, value: u64) -> Result; - fn visit_u128(&mut self, value: u128) -> Result; - fn visit_u256(&mut self, value: U256) -> Result; - fn visit_bool(&mut self, value: bool) -> Result; - fn visit_address(&mut self, value: AccountAddress) -> Result; - fn visit_signer(&mut self, value: AccountAddress) -> Result; + fn visit_u8( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u8, + ) -> Result; + + fn visit_u16( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u16, + ) -> Result; + + fn visit_u32( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u32, + ) -> Result; + + fn visit_u64( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u64, + ) -> Result; + + fn visit_u128( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u128, + ) -> Result; + + fn visit_u256( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: U256, + ) -> Result; + + fn visit_bool( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: bool, + ) -> Result; + + fn visit_address( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result; + + fn visit_signer( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result; fn visit_vector( &mut self, - driver: &mut VecDriver<'_, '_, '_>, + driver: &mut VecDriver<'_, 'b, 'l>, ) -> Result; fn visit_struct( &mut self, - driver: &mut StructDriver<'_, '_, '_>, + driver: &mut StructDriver<'_, 'b, 'l>, ) -> Result; fn visit_variant( &mut self, - driver: &mut VariantDriver<'_, '_, '_>, + driver: &mut VariantDriver<'_, 'b, 'l>, ) -> Result; } @@ -71,53 +115,89 @@ pub trait Visitor { /// Ok(()) /// } /// ``` -pub trait Traversal { +pub trait Traversal<'b, 'l> { type Error: From; - fn traverse_u8(&mut self, _value: u8) -> Result<(), Self::Error> { + fn traverse_u8( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u8, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_u16(&mut self, _value: u16) -> Result<(), Self::Error> { + fn traverse_u16( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u16, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_u32(&mut self, _value: u32) -> Result<(), Self::Error> { + fn traverse_u32( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u32, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_u64(&mut self, _value: u64) -> Result<(), Self::Error> { + fn traverse_u64( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u64, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_u128(&mut self, _value: u128) -> Result<(), Self::Error> { + fn traverse_u128( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u128, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_u256(&mut self, _value: U256) -> Result<(), Self::Error> { + fn traverse_u256( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: U256, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_bool(&mut self, _value: bool) -> Result<(), Self::Error> { + fn traverse_bool( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: bool, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_address(&mut self, _value: AccountAddress) -> Result<(), Self::Error> { + fn traverse_address( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: AccountAddress, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_signer(&mut self, _value: AccountAddress) -> Result<(), Self::Error> { + fn traverse_signer( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: AccountAddress, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_vector(&mut self, driver: &mut VecDriver<'_, '_, '_>) -> Result<(), Self::Error> { + fn traverse_vector(&mut self, driver: &mut VecDriver<'_, 'b, 'l>) -> Result<(), Self::Error> { while driver.next_element(self)?.is_some() {} Ok(()) } fn traverse_struct( &mut self, - driver: &mut StructDriver<'_, '_, '_>, + driver: &mut StructDriver<'_, 'b, 'l>, ) -> Result<(), Self::Error> { while driver.next_field(self)?.is_some() {} Ok(()) @@ -125,7 +205,7 @@ pub trait Traversal { fn traverse_variant( &mut self, - driver: &mut VariantDriver<'_, '_, '_>, + driver: &mut VariantDriver<'_, 'b, 'l>, ) -> Result<(), Self::Error> { while driver.next_field(self)?.is_some() {} Ok(()) @@ -306,15 +386,35 @@ impl<'r, 'b, 'l> VecDriver<'r, 'b, 'l> { } } -impl<'r, 'b, 'l> StructDriver<'r, 'b, 'l> { - fn new(bytes: &'r mut &'b [u8], layout: &'l MoveStructLayout) -> Self { +impl<'c, 'b, 'l> StructDriver<'c, 'b, 'l> { + fn new(inner: ValueDriver<'c, 'b, 'l>, layout: &'l MoveStructLayout) -> Self { Self { - bytes, + inner, layout, off: 0, } } + /// The offset at which the value being visited starts in the byte stream. + pub fn start(&self) -> usize { + self.inner.start() + } + + /// The current position in the byte stream. + pub fn position(&self) -> usize { + self.inner.position() + } + + /// All the bytes in the byte stream (including the ones that have been read). + pub fn bytes(&self) -> &'b [u8] { + self.inner.bytes() + } + + /// The bytes that haven't been consumed by the visitor yet. + pub fn remaining_bytes(&self) -> &'b [u8] { + self.inner.remaining_bytes() + } + /// The layout of the struct being visited. pub fn struct_layout(&self) -> &'l MoveStructLayout { self.layout @@ -343,7 +443,7 @@ impl<'r, 'b, 'l> StructDriver<'r, 'b, 'l> { return Ok(None); }; - let res = visit_value(self.bytes, &field.layout, visitor)?; + let res = visit_value(self.inner.bytes, &field.layout, visitor)?; self.off += 1; Ok(Some((field, res))) } @@ -357,16 +457,16 @@ impl<'r, 'b, 'l> StructDriver<'r, 'b, 'l> { } } -impl<'r, 'b, 'l> VariantDriver<'r, 'b, 'l> { +impl<'c, 'b, 'l> VariantDriver<'c, 'b, 'l> { fn new( - bytes: &'r mut &'b [u8], + inner: ValueDriver<'c, 'b, 'l>, layout: &'l MoveEnumLayout, variant_layout: &'l [MoveFieldLayout], variant_name: &'l IdentStr, tag: u16, ) -> Self { Self { - bytes, + inner, layout, tag, variant_name, @@ -375,6 +475,26 @@ impl<'r, 'b, 'l> VariantDriver<'r, 'b, 'l> { } } + /// The offset at which the value being visited starts in the byte stream. + pub fn start(&self) -> usize { + self.inner.start() + } + + /// The current position in the byte stream. + pub fn position(&self) -> usize { + self.inner.position() + } + + /// All the bytes in the byte stream (including the ones that have been read). + pub fn bytes(&self) -> &'b [u8] { + self.inner.bytes() + } + + /// The bytes that haven't been consumed by the visitor yet. + pub fn remaining_bytes(&self) -> &'b [u8] { + self.inner.remaining_bytes() + } + /// The layout of the enum being visited. pub fn enum_layout(&self) -> &'l MoveEnumLayout { self.layout @@ -418,7 +538,7 @@ impl<'r, 'b, 'l> VariantDriver<'r, 'b, 'l> { return Ok(None); }; - let res = visit_value(self.bytes, &field.layout, visitor)?; + let res = visit_value(self.inner.bytes, &field.layout, visitor)?; self.off += 1; Ok(Some((field, res))) } @@ -514,11 +634,3 @@ pub(crate) fn visit_variant( while driver.skip_field()?.is_some() {} Ok(res) } - -fn read_exact(bytes: &mut &[u8]) -> Result<[u8; N], Error> { - let mut buf = [0u8; N]; - bytes - .read_exact(&mut buf) - .map_err(|_| Error::UnexpectedEof)?; - Ok(buf) -} diff --git a/external-crates/move/crates/move-core-types/src/unit_tests/visitor_test.rs b/external-crates/move/crates/move-core-types/src/unit_tests/visitor_test.rs index 53514766888..edaef2899eb 100644 --- a/external-crates/move/crates/move-core-types/src/unit_tests/visitor_test.rs +++ b/external-crates/move/crates/move-core-types/src/unit_tests/visitor_test.rs @@ -11,7 +11,8 @@ use crate::{ MoveVariant, }, annotated_visitor::{ - self, NullTraversal, StructDriver, Traversal, VariantDriver, VecDriver, Visitor, + self, NullTraversal, StructDriver, Traversal, ValueDriver, VariantDriver, VecDriver, + Visitor, }, identifier::Identifier, language_storage::StructTag, @@ -27,57 +28,93 @@ fn traversal() { #[derive(Default)] struct CountingTraversal(usize); - impl Traversal for CountingTraversal { + impl<'b, 'l> Traversal<'b, 'l> for CountingTraversal { type Error = annotated_visitor::Error; - fn traverse_u8(&mut self, _: u8) -> Result<(), Self::Error> { + fn traverse_u8( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u8, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } - fn traverse_u16(&mut self, _: u16) -> Result<(), Self::Error> { + fn traverse_u16( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u16, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } - fn traverse_u32(&mut self, _: u32) -> Result<(), Self::Error> { + fn traverse_u32( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u32, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } - fn traverse_u64(&mut self, _: u64) -> Result<(), Self::Error> { + fn traverse_u64( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u64, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } - fn traverse_u128(&mut self, _: u128) -> Result<(), Self::Error> { + fn traverse_u128( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u128, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } - fn traverse_u256(&mut self, _: U256) -> Result<(), Self::Error> { + fn traverse_u256( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: U256, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } - fn traverse_bool(&mut self, _: bool) -> Result<(), Self::Error> { + fn traverse_bool( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: bool, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } - fn traverse_address(&mut self, _: AccountAddress) -> Result<(), Self::Error> { + fn traverse_address( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: AccountAddress, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } - fn traverse_signer(&mut self, _: AccountAddress) -> Result<(), Self::Error> { + fn traverse_signer( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: AccountAddress, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } fn traverse_vector( &mut self, - driver: &mut VecDriver<'_, '_, '_>, + driver: &mut VecDriver<'_, 'b, 'l>, ) -> Result<(), Self::Error> { self.0 += 1; while driver.next_element(self)?.is_some() {} @@ -86,7 +123,7 @@ fn traversal() { fn traverse_struct( &mut self, - driver: &mut StructDriver<'_, '_, '_>, + driver: &mut StructDriver<'_, 'b, 'l>, ) -> Result<(), Self::Error> { self.0 += 1; while driver.next_field(self)?.is_some() {} @@ -95,7 +132,7 @@ fn traversal() { fn traverse_variant( &mut self, - driver: &mut VariantDriver<'_, '_, '_>, + driver: &mut VariantDriver<'_, 'b, 'l>, ) -> Result<(), Self::Error> { self.0 += 1; while driver.next_field(self)?.is_some() {} @@ -304,58 +341,94 @@ fn nested_datatype_visit() { output: String, } - impl Visitor for PrintVisitor { + impl<'b, 'l> Visitor<'b, 'l> for PrintVisitor { type Value = MoveValue; type Error = annotated_visitor::Error; - fn visit_u8(&mut self, value: u8) -> Result { + fn visit_u8( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u8, + ) -> Result { write!(self.output, "\n[{}] {value}: u8", self.depth).unwrap(); Ok(V::U8(value)) } - fn visit_u16(&mut self, value: u16) -> Result { + fn visit_u16( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u16, + ) -> Result { write!(self.output, "\n[{}] {value}: u16", self.depth).unwrap(); Ok(V::U16(value)) } - fn visit_u32(&mut self, value: u32) -> Result { + fn visit_u32( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u32, + ) -> Result { write!(self.output, "\n[{}] {value}: u32", self.depth).unwrap(); Ok(V::U32(value)) } - fn visit_u64(&mut self, value: u64) -> Result { + fn visit_u64( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u64, + ) -> Result { write!(self.output, "\n[{}] {value}: u64", self.depth).unwrap(); Ok(V::U64(value)) } - fn visit_u128(&mut self, value: u128) -> Result { + fn visit_u128( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u128, + ) -> Result { write!(self.output, "\n[{}] {value}: u128", self.depth).unwrap(); Ok(V::U128(value)) } - fn visit_u256(&mut self, value: U256) -> Result { + fn visit_u256( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: U256, + ) -> Result { write!(self.output, "\n[{}] {value}: u256", self.depth).unwrap(); Ok(V::U256(value)) } - fn visit_bool(&mut self, value: bool) -> Result { + fn visit_bool( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: bool, + ) -> Result { write!(self.output, "\n[{}] {value}: bool", self.depth).unwrap(); Ok(V::Bool(value)) } - fn visit_address(&mut self, value: AccountAddress) -> Result { + fn visit_address( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result { write!(self.output, "\n[{}] {value}: address", self.depth).unwrap(); Ok(V::Address(value)) } - fn visit_signer(&mut self, value: AccountAddress) -> Result { + fn visit_signer( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result { write!(self.output, "\n[{}] {value}: signer", self.depth).unwrap(); Ok(V::Signer(value)) } fn visit_vector( &mut self, - driver: &mut VecDriver<'_, '_, '_>, + driver: &mut VecDriver<'_, 'b, 'l>, ) -> Result { let layout = driver.element_layout(); write!(self.output, "\n[{}] vector<{layout:#}>", self.depth).unwrap(); @@ -376,7 +449,7 @@ fn nested_datatype_visit() { fn visit_struct( &mut self, - driver: &mut StructDriver<'_, '_, '_>, + driver: &mut StructDriver<'_, 'b, 'l>, ) -> Result { let layout = driver.struct_layout(); write!(self.output, "\n[{}] {layout:#}", self.depth).unwrap(); @@ -398,7 +471,7 @@ fn nested_datatype_visit() { fn visit_variant( &mut self, - driver: &mut VariantDriver<'_, '_, '_>, + driver: &mut VariantDriver<'_, 'b, 'l>, ) -> Result { let layout = driver.enum_layout(); write!(self.output, "\n[{}] {layout:#}", self.depth).unwrap(); @@ -522,17 +595,21 @@ fn peek_field_test() { fields: &'f [&'f str], } - impl<'f> Visitor for PeekU64Visitor<'f> { + impl<'b, 'l, 'f> Visitor<'b, 'l> for PeekU64Visitor<'f> { type Value = Option; type Error = annotated_visitor::Error; - fn visit_u64(&mut self, value: u64) -> Result { + fn visit_u64( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u64, + ) -> Result { Ok(self.fields.is_empty().then_some(value)) } fn visit_struct( &mut self, - driver: &mut StructDriver<'_, '_, '_>, + driver: &mut StructDriver<'_, 'b, 'l>, ) -> Result { let [field, fields @ ..] = self.fields else { return Ok(None); @@ -553,7 +630,7 @@ fn peek_field_test() { fn visit_variant( &mut self, - driver: &mut VariantDriver<'_, '_, '_>, + driver: &mut VariantDriver<'_, 'b, 'l>, ) -> Result { let [field, fields @ ..] = self.fields else { return Ok(None); @@ -574,35 +651,67 @@ fn peek_field_test() { // === Empty/default cases === - fn visit_u8(&mut self, _: u8) -> Result { + fn visit_u8( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: u8, + ) -> Result { Ok(None) } - fn visit_u16(&mut self, _: u16) -> Result { + fn visit_u16( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: u16, + ) -> Result { Ok(None) } - fn visit_u32(&mut self, _: u32) -> Result { + fn visit_u32( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: u32, + ) -> Result { Ok(None) } - fn visit_u128(&mut self, _: u128) -> Result { + fn visit_u128( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: u128, + ) -> Result { Ok(None) } - fn visit_u256(&mut self, _: U256) -> Result { + fn visit_u256( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: U256, + ) -> Result { Ok(None) } - fn visit_bool(&mut self, _: bool) -> Result { + fn visit_bool( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: bool, + ) -> Result { Ok(None) } - fn visit_address(&mut self, _: AccountAddress) -> Result { + fn visit_address( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: AccountAddress, + ) -> Result { Ok(None) } - fn visit_signer(&mut self, _: AccountAddress) -> Result { + fn visit_signer( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: AccountAddress, + ) -> Result { Ok(None) } @@ -672,6 +781,284 @@ fn peek_field_test() { assert_eq!(visit_struct(&["f", "h"]), Some(46)); } +#[test] +fn byte_offset_test() { + use MoveTypeLayout as T; + use MoveValue as V; + + #[derive(Default)] + struct ByteOffsetVisitor(String); + + impl<'b, 'l> Traversal<'b, 'l> for ByteOffsetVisitor { + type Error = annotated_visitor::Error; + + fn traverse_u8( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u8, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {value}: u8", + driver.start(), + driver.position() + ) + .unwrap(); + Ok(()) + } + + fn traverse_u16( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u16, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {value}: u16", + driver.start(), + driver.position() + ) + .unwrap(); + Ok(()) + } + + fn traverse_u32( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u32, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {value}: u32", + driver.start(), + driver.position() + ) + .unwrap(); + Ok(()) + } + + fn traverse_u64( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u64, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {value}: u64", + driver.start(), + driver.position() + ) + .unwrap(); + Ok(()) + } + + fn traverse_u128( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u128, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {value}: u128", + driver.start(), + driver.position() + ) + .unwrap(); + Ok(()) + } + + fn traverse_u256( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: U256, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {value}: u256", + driver.start(), + driver.position() + ) + .unwrap(); + Ok(()) + } + + fn traverse_bool( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: bool, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {value}: bool", + driver.start(), + driver.position() + ) + .unwrap(); + Ok(()) + } + + fn traverse_address( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {}: address", + driver.start(), + driver.position(), + value.to_canonical_display(/* with_prefix */ true), + ) + .unwrap(); + Ok(()) + } + + fn traverse_signer( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {}: address", + driver.start(), + driver.position(), + value.to_canonical_display(/* with_prefix */ true), + ) + .unwrap(); + Ok(()) + } + + fn traverse_vector( + &mut self, + driver: &mut VecDriver<'_, 'b, 'l>, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] vector<{:#}>", + driver.start(), + driver.position(), + driver.element_layout(), + ) + .unwrap(); + while driver.next_element(self)?.is_some() {} + Ok(()) + } + + fn traverse_struct( + &mut self, + driver: &mut StructDriver<'_, 'b, 'l>, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {:#}", + driver.start(), + driver.position(), + driver.struct_layout(), + ) + .unwrap(); + + while let Some((_, ())) = driver.next_field(self)? {} + Ok(()) + } + + fn traverse_variant( + &mut self, + driver: &mut VariantDriver<'_, 'b, 'l>, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {:#}", + driver.start(), + driver.position(), + driver.enum_layout(), + ) + .unwrap(); + + while let Some((_, ())) = driver.next_field(self)? {} + Ok(()) + } + } + + let type_layout = struct_layout_( + "0x0::foo::Bar", + vec![ + ( + "inner", + struct_layout_( + "0x0::baz::Qux", + vec![("f", T::U64), ("g", T::Vector(Box::new(T::U32)))], + ), + ), + ( + "last", + enum_layout_("0x0::foo::Baz", vec![("e", vec![("h", T::U64)])]), + ), + ], + ); + + let T::Struct(struct_layout) = &type_layout else { + panic!("Not a struct layout"); + }; + + let bytes = serialize(struct_value_( + "0x0::foo::Bar", + vec![ + ( + "inner", + struct_value_( + "0x0::baz::Qux", + vec![ + ("f", V::U64(7)), + ("g", V::Vector(vec![V::U32(1), V::U32(2), V::U32(3)])), + ], + ), + ), + ( + "last", + variant_value_("0x0::foo::Baz", "e", 0, vec![("h", V::U64(4))]), + ), + ], + )); + + let mut value_visitor = ByteOffsetVisitor::default(); + MoveValue::visit_deserialize(&bytes, &type_layout, &mut value_visitor).unwrap(); + + let mut struct_visitor = ByteOffsetVisitor::default(); + MoveStruct::visit_deserialize(&bytes, struct_layout, &mut struct_visitor).unwrap(); + + let expected_output = r#" +[ 0 .. 0] struct 0x0::foo::Bar { + inner: struct 0x0::baz::Qux { + f: u64, + g: vector, + }, + last: enum 0x0::foo::Baz { + e { + h: u64, + }, + }, +} +[ 0 .. 0] struct 0x0::baz::Qux { + f: u64, + g: vector, +} +[ 0 .. 8] 7: u64 +[ 8 .. 9] vector +[ 9 .. 13] 1: u32 +[ 13 .. 17] 2: u32 +[ 17 .. 21] 3: u32 +[ 21 .. 22] enum 0x0::foo::Baz { + e { + h: u64, + }, +} +[ 22 .. 30] 4: u64"#; + + assert_eq!(value_visitor.0, expected_output); + assert_eq!(struct_visitor.0, expected_output); +} + /// Create a struct value for test purposes. fn struct_value_(rep: &str, fields: Vec<(&str, MoveValue)>) -> MoveValue { let type_ = StructTag::from_str(rep).unwrap(); diff --git a/iota-execution/latest/iota-move-natives/src/test_scenario.rs b/iota-execution/latest/iota-move-natives/src/test_scenario.rs index ca4243359c9..784f46e6a9b 100644 --- a/iota-execution/latest/iota-move-natives/src/test_scenario.rs +++ b/iota-execution/latest/iota-move-natives/src/test_scenario.rs @@ -25,8 +25,8 @@ use iota_types::{ use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_core_types::{ account_address::AccountAddress, - annotated_value::{MoveStruct, MoveValue, MoveVariant}, - identifier::Identifier, + annotated_value::{MoveFieldLayout, MoveStructLayout, MoveTypeLayout, MoveValue}, + annotated_visitor as AV, language_storage::StructTag, vm_status::StatusCode, }; diff --git a/scripts/get_all_dependencies_of_pr.py b/scripts/get_all_dependencies_of_pr.py new file mode 100644 index 00000000000..6b12673a479 --- /dev/null +++ b/scripts/get_all_dependencies_of_pr.py @@ -0,0 +1,117 @@ +import subprocess +import re +from datetime import datetime + +# Configuration: Set these variables before running the script +REPO_PATH = "$HOME/works/sui" # Source repository (Sui) +INITIAL_COMMIT = "a5eab1a" # Initial commit of the last rebase +BASE_COMMIT = "fe8982b" # Base commit of the PR in the Sui repository +TARGET_COMMIT = "045352d" # Target commit of the PR in the Sui repository +REPO_URL = "https://github.com/MystenLabs/sui" # Replace with Sui repository URL +OUTPUT_FILE = "pr_dependencies.txt" # Output file to save the PR dependencies + +def run_command(cmd): + """Run a shell command and return its output.""" + try: + result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True) + return result.stdout.strip() + except subprocess.CalledProcessError as e: + print(f"Command failed: {' '.join(cmd)}\nError: {e.stderr}") + return "" + +def get_changed_files(base_commit, target_commit): + """List changed files between base and target commits (base excluded, target included).""" + cmd = ["git", "diff", "--name-only", f"{base_commit}..{target_commit}"] + return run_command(cmd).splitlines() + +def get_commit_history(file_path, initial_commit, target_commit): + """Get the commit history for a file from initial to target commit.""" + cmd = ["git", "log", "--pretty=format:%H %s", "--follow", f"{initial_commit}..{target_commit}", "--", file_path] + return run_command(cmd).splitlines() + +def extract_pr_numbers(commit_message): + """Extract PR numbers from commit messages.""" + pr_pattern = r"#(\d+)" # Assuming PRs are referenced as (#123) + return re.findall(pr_pattern, commit_message) + +def get_pr_details(pr_number, initial_commit, target_commit): + """Get details for a given PR, including its changed files and commit time.""" + cmd = ["git", "log", "--pretty=format:%H %ct", f"--grep=#{pr_number}", f"{initial_commit}..{target_commit}"] + commit_info = run_command(cmd).splitlines() + if not commit_info: + return None + latest_commit, commit_time = commit_info[0].split() + changed_files = get_changed_files_from_pr(pr_number, initial_commit, target_commit) + return { + "pr_number": pr_number, + "url": f"{REPO_URL}/pull/{pr_number}", + "changed_files": sorted(changed_files) if changed_files else ["No files found"], + "commit_time": int(commit_time) + } + +def get_changed_files_from_pr(pr_number, initial_commit, target_commit): + """Get changed files for a given PR, filtered by the commit range.""" + cmd = ["git", "log", "--pretty=format:%H", f"--grep=#{pr_number}", f"{initial_commit}..{target_commit}"] + pr_commits = run_command(cmd).splitlines() + changed_files = set() + for commit in pr_commits: + cmd = ["git", "show", "--pretty=", "--name-only", commit] + files = run_command(cmd).splitlines() + changed_files.update(files) + return changed_files + +def get_all_dependent_prs(initial_commit, base_commit, target_commit): + """Retrieve all dependent PRs for the changed files.""" + processed_files = set() + processed_prs = set() + all_prs = [] + files_to_process = set(get_changed_files(base_commit, target_commit)) + + while files_to_process: + new_files = set() + for file in files_to_process: + if file in processed_files: + continue + print(f"Processing file: {file}") + processed_files.add(file) + commit_history = get_commit_history(file, initial_commit, target_commit) + for commit in commit_history: + pr_numbers = extract_pr_numbers(commit) + for pr_number in pr_numbers: + if pr_number not in processed_prs: + processed_prs.add(pr_number) + pr_details = get_pr_details(pr_number, initial_commit, target_commit) + if pr_details: + all_prs.append(pr_details) + pr_files = get_changed_files_from_pr(pr_number, initial_commit, target_commit) + new_files.update(pr_files - processed_files) + files_to_process = new_files + + # Sort PRs by commit time (ascending) + sorted_prs = sorted(all_prs, key=lambda x: x["commit_time"]) + return sorted_prs + +def save_pr_dependencies_to_file(pr_dependencies, output_file): + """Save the PR dependencies to a file.""" + with open(output_file, "w") as file: + file.write("PR Dependencies (sorted by commit time):\n") + for pr in pr_dependencies: + commit_time = datetime.utcfromtimestamp(pr["commit_time"]).strftime('%Y-%m-%d %H:%M:%S') + file.write(f"PR #{pr['pr_number']}: {pr['url']}\n") + file.write(f"Commit Time: {commit_time}\n") + file.write("Changed Files:\n") + for changed_file in pr["changed_files"]: + file.write(f" - {changed_file}\n") + file.write("\n") + print(f"PR dependencies saved to {output_file}") + +if __name__ == "__main__": + # Navigate to the repository + print(f"Starting to find dependent PRs...") + subprocess.run(["cd", REPO_PATH], shell=True, check=False) + + # Retrieve all dependent PRs + dependent_prs = get_all_dependent_prs(INITIAL_COMMIT, BASE_COMMIT, TARGET_COMMIT) + + # Save to file + save_pr_dependencies_to_file(dependent_prs, OUTPUT_FILE)