From fa46aa9caa42871c018da94220fde0327cc2f378 Mon Sep 17 00:00:00 2001 From: Tilak Madichetti Date: Mon, 8 Apr 2024 15:06:16 +0530 Subject: [PATCH] Enhancement/renaming thread (#289) --- .../{parent_chain.rs => ancestral_line.rs} | 10 ++++----- ...{closest_parent.rs => closest_ancestor.rs} | 10 ++++----- aderyn_core/src/context/browser/mod.rs | 8 +++---- aderyn_core/src/context/browser/sort_nodes.rs | 14 ++++++------ aderyn_core/src/context/workspace_context.rs | 4 ++-- .../{parent_chain.rs => ancestral_line.rs} | 20 +++++++++--------- ...{closest_parent.rs => closest_ancestor.rs} | 20 +++++++++--------- aderyn_core/src/detect/experimental/mod.rs | 4 ++-- .../src/detect/low/deprecated_oz_functions.rs | 6 +++--- .../detect/medium/unsafe_oz_erc721_mint.rs | 4 ++-- aderyn_core/src/detect/nc/empty_blocks.rs | 6 +++--- .../src/detect/nc/zero_address_check.rs | 4 ++-- nyth/archive.zip | Bin 17047 -> 17047 bytes 13 files changed, 55 insertions(+), 55 deletions(-) rename aderyn_core/src/context/browser/{parent_chain.rs => ancestral_line.rs} (68%) rename aderyn_core/src/context/browser/{closest_parent.rs => closest_ancestor.rs} (79%) rename aderyn_core/src/detect/experimental/{parent_chain.rs => ancestral_line.rs} (80%) rename aderyn_core/src/detect/experimental/{closest_parent.rs => closest_ancestor.rs} (79%) diff --git a/aderyn_core/src/context/browser/parent_chain.rs b/aderyn_core/src/context/browser/ancestral_line.rs similarity index 68% rename from aderyn_core/src/context/browser/parent_chain.rs rename to aderyn_core/src/context/browser/ancestral_line.rs index c02dab52c..bf7abf417 100644 --- a/aderyn_core/src/context/browser/parent_chain.rs +++ b/aderyn_core/src/context/browser/ancestral_line.rs @@ -4,9 +4,9 @@ use crate::{ visitor::ast_visitor::{ASTConstVisitor, Node}, }; -pub trait GetParentChain { +pub trait GetAncestralLine { /// Get the parent Chain of an ASTNode - fn parent_chain<'a>(&self, context: &'a WorkspaceContext) -> Option>; + fn ancestral_line<'a>(&self, context: &'a WorkspaceContext) -> Option>; } #[derive(Default)] @@ -21,8 +21,8 @@ impl ASTConstVisitor for NodeIDReceiver { } } -impl GetParentChain for T { - fn parent_chain<'a>(&self, context: &'a WorkspaceContext) -> Option> { +impl GetAncestralLine for T { + fn ancestral_line<'a>(&self, context: &'a WorkspaceContext) -> Option> { // Setup a Node ID receiver let mut node_id_receiver = NodeIDReceiver::default(); @@ -30,6 +30,6 @@ impl GetParentChain for T { self.accept_id(&mut node_id_receiver).ok()?; let current_node_id = node_id_receiver.id?; - Some(context.get_parent_chain(current_node_id)) + Some(context.get_ancestral_line(current_node_id)) } } diff --git a/aderyn_core/src/context/browser/closest_parent.rs b/aderyn_core/src/context/browser/closest_ancestor.rs similarity index 79% rename from aderyn_core/src/context/browser/closest_parent.rs rename to aderyn_core/src/context/browser/closest_ancestor.rs index efb1c2d63..51428b991 100644 --- a/aderyn_core/src/context/browser/closest_parent.rs +++ b/aderyn_core/src/context/browser/closest_ancestor.rs @@ -4,9 +4,9 @@ use crate::{ visitor::ast_visitor::{ASTConstVisitor, Node}, }; -pub trait GetClosestParentOfTypeX { +pub trait GetClosestAncestorOfTypeX { /// Get the parent Chain of an ASTNode - fn closest_parent_of_type<'a>( + fn closest_ancestor_of_type<'a>( &self, context: &'a WorkspaceContext, node_type: NodeType, @@ -25,8 +25,8 @@ impl ASTConstVisitor for NodeIDReceiver { } } -impl GetClosestParentOfTypeX for T { - fn closest_parent_of_type<'a>( +impl GetClosestAncestorOfTypeX for T { + fn closest_ancestor_of_type<'a>( &self, context: &'a WorkspaceContext, node_type: NodeType, @@ -37,6 +37,6 @@ impl GetClosestParentOfTypeX for T { // Find the ID of the node this method is called upon self.accept_id(&mut node_id_receiver).ok()?; let current_node_id = node_id_receiver.id?; - context.get_closest_parent(current_node_id, node_type) + context.get_closest_ancestor(current_node_id, node_type) } } diff --git a/aderyn_core/src/context/browser/mod.rs b/aderyn_core/src/context/browser/mod.rs index 40301b392..d6987af8f 100644 --- a/aderyn_core/src/context/browser/mod.rs +++ b/aderyn_core/src/context/browser/mod.rs @@ -1,14 +1,14 @@ -mod closest_parent; +mod ancestral_line; +mod closest_ancestor; mod extractor; mod immediate_children; mod location; mod parent; -mod parent_chain; mod sort_nodes; -pub use closest_parent::*; +pub use ancestral_line::*; +pub use closest_ancestor::*; pub use extractor::*; pub use immediate_children::*; pub use location::*; pub use parent::*; -pub use parent_chain::*; pub use sort_nodes::*; diff --git a/aderyn_core/src/context/browser/sort_nodes.rs b/aderyn_core/src/context/browser/sort_nodes.rs index 210e94f5b..5bcb0c5cf 100644 --- a/aderyn_core/src/context/browser/sort_nodes.rs +++ b/aderyn_core/src/context/browser/sort_nodes.rs @@ -3,23 +3,23 @@ use std::collections::BTreeSet; use crate::context::workspace_context::{ASTNode, WorkspaceContext}; pub trait SortNodeReferencesToSequence<'a> { - fn sort_by_line_nos(self, context: &'a WorkspaceContext) -> Option>; + fn sort_by_src_position(self, context: &'a WorkspaceContext) -> Option>; } pub trait SortOwnedNodesToSequence<'a> { - fn sort_by_line_nos(self, context: &'a WorkspaceContext) -> Option>; + fn sort_by_src_position(self, context: &'a WorkspaceContext) -> Option>; } impl<'a> SortNodeReferencesToSequence<'a> for &[&'a ASTNode] { - fn sort_by_line_nos(self, context: &'a WorkspaceContext) -> Option> { - sort_by_line_nos(self, context) + fn sort_by_src_position(self, context: &'a WorkspaceContext) -> Option> { + sort_by_src_position(self, context) } } impl<'a> SortOwnedNodesToSequence<'a> for &[ASTNode] { - fn sort_by_line_nos(self, context: &'a WorkspaceContext) -> Option> { + fn sort_by_src_position(self, context: &'a WorkspaceContext) -> Option> { let nodes = self.iter().collect::>(); - let sorted = sort_by_line_nos(&nodes, context); + let sorted = sort_by_src_position(&nodes, context); if let Some(sorted_nodes) = sorted { let owned_nodes = sorted_nodes.iter().map(|&x| x.clone()).collect::>(); return Some(owned_nodes); @@ -28,7 +28,7 @@ impl<'a> SortOwnedNodesToSequence<'a> for &[ASTNode] { } } -fn sort_by_line_nos<'a>( +fn sort_by_src_position<'a>( nodes: &[&'a ASTNode], context: &'a WorkspaceContext, ) -> Option> { diff --git a/aderyn_core/src/context/workspace_context.rs b/aderyn_core/src/context/workspace_context.rs index c8c261a3b..7816899dc 100644 --- a/aderyn_core/src/context/workspace_context.rs +++ b/aderyn_core/src/context/workspace_context.rs @@ -1216,7 +1216,7 @@ impl WorkspaceContext { self.nodes.get(self.parent_link.get(&node_id)?) } - pub fn get_parent_chain(&self, node_id: NodeID) -> Vec<&ASTNode> { + pub fn get_ancestral_line(&self, node_id: NodeID) -> Vec<&ASTNode> { let mut chain = vec![]; let mut parent = self.nodes.get(&node_id); while let Some(next_parent) = parent { @@ -1225,7 +1225,7 @@ impl WorkspaceContext { } chain } - pub fn get_closest_parent(&self, node_id: NodeID, node_type: NodeType) -> Option<&ASTNode> { + pub fn get_closest_ancestor(&self, node_id: NodeID, node_type: NodeType) -> Option<&ASTNode> { let mut current_node_id = self.parent_link.get(&node_id)?; while let Some(current) = self.nodes.get(current_node_id) { if current.node_type() == node_type { diff --git a/aderyn_core/src/detect/experimental/parent_chain.rs b/aderyn_core/src/detect/experimental/ancestral_line.rs similarity index 80% rename from aderyn_core/src/detect/experimental/parent_chain.rs rename to aderyn_core/src/detect/experimental/ancestral_line.rs index aed6157d2..ebb430b4d 100644 --- a/aderyn_core/src/detect/experimental/parent_chain.rs +++ b/aderyn_core/src/detect/experimental/ancestral_line.rs @@ -5,7 +5,7 @@ use crate::{ ast::NodeID, capture, context::{ - browser::{GetParentChain, SortNodeReferencesToSequence}, + browser::{GetAncestralLine, SortNodeReferencesToSequence}, workspace_context::{ASTNode, WorkspaceContext}, }, detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity}, @@ -13,7 +13,7 @@ use crate::{ use eyre::Result; #[derive(Default)] -pub struct ParentChainDemonstrator { +pub struct AncestralLineDemonstrator { // Keys are source file name and line number found_instances: BTreeMap<(String, usize, String), NodeID>, } @@ -23,12 +23,12 @@ pub struct ParentChainDemonstrator { In ParentChainContract.sol, there is only 1 assignment done. The goal is to capture it first, second and third parent */ -impl IssueDetector for ParentChainDemonstrator { +impl IssueDetector for AncestralLineDemonstrator { fn detect(&mut self, context: &WorkspaceContext) -> Result> { for assignment in context.assignments() { capture!(self, context, assignment); - if let Some(parent_chain) = assignment.parent_chain(context) { + if let Some(parent_chain) = assignment.ancestral_line(context) { if let ASTNode::Block(_) = parent_chain[1] { capture!(self, context, parent_chain[1]); } @@ -40,8 +40,8 @@ impl IssueDetector for ParentChainDemonstrator { } } - if let Some(mut parent_chain) = assignment.parent_chain(context) { - let sorted_chain = parent_chain.sort_by_line_nos(context).unwrap(); + if let Some(mut parent_chain) = assignment.ancestral_line(context) { + let sorted_chain = parent_chain.sort_by_src_position(context).unwrap(); parent_chain.reverse(); assert_eq!(sorted_chain, parent_chain); } @@ -72,19 +72,19 @@ impl IssueDetector for ParentChainDemonstrator { } #[cfg(test)] -mod parent_chain_demo_tests { +mod ancestral_line_demo_tests { use crate::detect::{ detector::{detector_test_helpers::load_contract, IssueDetector}, - experimental::parent_chain::ParentChainDemonstrator, + experimental::ancestral_line::AncestralLineDemonstrator, }; #[test] - fn test_parent_chain_demo() { + fn test_ancestral_line_demo() { let context = load_contract( "../tests/contract-playground/out/ParentChainContract.sol/ParentChainContract.json", ); - let mut detector = ParentChainDemonstrator::default(); + let mut detector = AncestralLineDemonstrator::default(); let found = detector.detect(&context).unwrap(); assert!(found); diff --git a/aderyn_core/src/detect/experimental/closest_parent.rs b/aderyn_core/src/detect/experimental/closest_ancestor.rs similarity index 79% rename from aderyn_core/src/detect/experimental/closest_parent.rs rename to aderyn_core/src/detect/experimental/closest_ancestor.rs index 664086edf..4db01dec4 100644 --- a/aderyn_core/src/detect/experimental/closest_parent.rs +++ b/aderyn_core/src/detect/experimental/closest_ancestor.rs @@ -5,7 +5,7 @@ use crate::{ ast::{NodeID, NodeType}, capture, context::{ - browser::GetClosestParentOfTypeX, + browser::GetClosestAncestorOfTypeX, workspace_context::{ASTNode, WorkspaceContext}, }, detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity}, @@ -13,7 +13,7 @@ use crate::{ use eyre::Result; #[derive(Default)] -pub struct ClosestParentDemonstrator { +pub struct ClosestAncestorDemonstrator { // Keys are source file name and line number found_instances: BTreeMap<(String, usize, String), NodeID>, } @@ -23,24 +23,24 @@ pub struct ClosestParentDemonstrator { In ParentChainContract.sol, there is only 1 assignment done. The goal is to capture it first, second and third parent */ -impl IssueDetector for ClosestParentDemonstrator { +impl IssueDetector for ClosestAncestorDemonstrator { fn detect(&mut self, context: &WorkspaceContext) -> Result> { for assignment in context.assignments() { capture!(self, context, assignment); if let Some(ASTNode::Block(block)) = - assignment.closest_parent_of_type(context, NodeType::Block) + assignment.closest_ancestor_of_type(context, NodeType::Block) { capture!(self, context, block); } if let Some(for_statement) = - assignment.closest_parent_of_type(context, NodeType::ForStatement) + assignment.closest_ancestor_of_type(context, NodeType::ForStatement) { capture!(self, context, for_statement); if let Some(ASTNode::Block(block)) = - for_statement.closest_parent_of_type(context, NodeType::Block) + for_statement.closest_ancestor_of_type(context, NodeType::Block) { capture!(self, context, block); } @@ -72,19 +72,19 @@ impl IssueDetector for ClosestParentDemonstrator { } #[cfg(test)] -mod parent_chain_demo_tests { +mod closest_ancestor_demo_tests { use crate::detect::{ detector::{detector_test_helpers::load_contract, IssueDetector}, - experimental::closest_parent::ClosestParentDemonstrator, + experimental::closest_ancestor::ClosestAncestorDemonstrator, }; #[test] - fn test_closest_parent() { + fn test_closest_ancestor() { let context = load_contract( "../tests/contract-playground/out/ParentChainContract.sol/ParentChainContract.json", ); - let mut detector = ClosestParentDemonstrator::default(); + let mut detector = ClosestAncestorDemonstrator::default(); let found = detector.detect(&context).unwrap(); assert!(found); diff --git a/aderyn_core/src/detect/experimental/mod.rs b/aderyn_core/src/detect/experimental/mod.rs index 8e1498583..a2c3ef6b3 100644 --- a/aderyn_core/src/detect/experimental/mod.rs +++ b/aderyn_core/src/detect/experimental/mod.rs @@ -1,4 +1,4 @@ -pub(crate) mod closest_parent; +pub(crate) mod ancestral_line; +pub(crate) mod closest_ancestor; pub(crate) mod immediate_children; pub(crate) mod immediate_parent; -pub(crate) mod parent_chain; diff --git a/aderyn_core/src/detect/low/deprecated_oz_functions.rs b/aderyn_core/src/detect/low/deprecated_oz_functions.rs index 571c6114a..ea277b0b2 100644 --- a/aderyn_core/src/detect/low/deprecated_oz_functions.rs +++ b/aderyn_core/src/detect/low/deprecated_oz_functions.rs @@ -4,7 +4,7 @@ use crate::{ ast::{NodeID, NodeType}, capture, context::{ - browser::GetClosestParentOfTypeX, + browser::GetClosestAncestorOfTypeX, workspace_context::{ASTNode, WorkspaceContext}, }, detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity}, @@ -23,7 +23,7 @@ impl IssueDetector for DeprecatedOZFunctionsDetector { // if source_unit has any ImportDirectives with absolute_path containing "openzeppelin" // call identifier.accept(self) if let Some(ASTNode::SourceUnit(source_unit)) = - identifier.closest_parent_of_type(context, NodeType::SourceUnit) + identifier.closest_ancestor_of_type(context, NodeType::SourceUnit) { let import_directives = source_unit.import_directives(); if import_directives.iter().any(|directive| { @@ -43,7 +43,7 @@ impl IssueDetector for DeprecatedOZFunctionsDetector { // if source_unit has any ImportDirectives with absolute_path containing "openzeppelin" // call member_access.accept(self) if let Some(ASTNode::SourceUnit(source_unit)) = - member_access.closest_parent_of_type(context, NodeType::SourceUnit) + member_access.closest_ancestor_of_type(context, NodeType::SourceUnit) { let import_directives = source_unit.import_directives(); if import_directives.iter().any(|directive| { diff --git a/aderyn_core/src/detect/medium/unsafe_oz_erc721_mint.rs b/aderyn_core/src/detect/medium/unsafe_oz_erc721_mint.rs index 838e2de27..3899d191a 100644 --- a/aderyn_core/src/detect/medium/unsafe_oz_erc721_mint.rs +++ b/aderyn_core/src/detect/medium/unsafe_oz_erc721_mint.rs @@ -4,7 +4,7 @@ use crate::{ ast::{NodeID, NodeType}, capture, context::{ - browser::GetClosestParentOfTypeX, + browser::GetClosestAncestorOfTypeX, workspace_context::{ASTNode, WorkspaceContext}, }, detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity}, @@ -23,7 +23,7 @@ impl IssueDetector for UnsafeERC721MintDetector { // if source_unit has any ImportDirectives with absolute_path containing "openzeppelin" // call identifier.accept(self) if let Some(ASTNode::SourceUnit(source_unit)) = - identifier.closest_parent_of_type(context, NodeType::SourceUnit) + identifier.closest_ancestor_of_type(context, NodeType::SourceUnit) { let import_directives = source_unit.import_directives(); if import_directives.iter().any(|directive| { diff --git a/aderyn_core/src/detect/nc/empty_blocks.rs b/aderyn_core/src/detect/nc/empty_blocks.rs index a36717540..52ab6075d 100644 --- a/aderyn_core/src/detect/nc/empty_blocks.rs +++ b/aderyn_core/src/detect/nc/empty_blocks.rs @@ -4,7 +4,7 @@ use crate::{ ast::{FunctionKind, NodeID, NodeType}, capture, context::{ - browser::{GetClosestParentOfTypeX, GetParentChain}, + browser::{GetAncestralLine, GetClosestAncestorOfTypeX}, workspace_context::{ASTNode, WorkspaceContext}, }, detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity}, @@ -21,7 +21,7 @@ impl IssueDetector for EmptyBlockDetector { fn detect(&mut self, context: &WorkspaceContext) -> Result> { for empty_block in context.blocks().iter().filter(|b| b.statements.is_empty()) { if let Some(ASTNode::FunctionDefinition(f)) = - &empty_block.closest_parent_of_type(context, NodeType::FunctionDefinition) + &empty_block.closest_ancestor_of_type(context, NodeType::FunctionDefinition) { // It's okay to have empty block if it's a constructor, receive, or fallback if f.kind == FunctionKind::Function { @@ -31,7 +31,7 @@ impl IssueDetector for EmptyBlockDetector { || f.kind == FunctionKind::Fallback { // It's not okay to have empty block nested somewhere inside constructor - if let Some(block_chain) = empty_block.parent_chain(context) { + if let Some(block_chain) = empty_block.ancestral_line(context) { let function_definition_index = block_chain .iter() .position(|x| x.node_type() == NodeType::FunctionDefinition) diff --git a/aderyn_core/src/detect/nc/zero_address_check.rs b/aderyn_core/src/detect/nc/zero_address_check.rs index 170c6e567..5c0f537c5 100644 --- a/aderyn_core/src/detect/nc/zero_address_check.rs +++ b/aderyn_core/src/detect/nc/zero_address_check.rs @@ -187,7 +187,7 @@ impl IssueDetector for ZeroAddressCheckDetector { mod zero_address_check_tests { use crate::{ ast::NodeType, - context::{browser::GetClosestParentOfTypeX, workspace_context::ASTNode}, + context::{browser::GetClosestAncestorOfTypeX, workspace_context::ASTNode}, detect::{ detector::{detector_test_helpers::load_contract, IssueDetector}, nc::zero_address_check::ZeroAddressCheckDetector, @@ -209,7 +209,7 @@ mod zero_address_check_tests { for node_id in detector.instances().values() { if let ASTNode::Assignment(assignment) = context.nodes.get(node_id).unwrap() { if let ASTNode::FunctionDefinition(function) = assignment - .closest_parent_of_type(&context, NodeType::FunctionDefinition) + .closest_ancestor_of_type(&context, NodeType::FunctionDefinition) .unwrap() { assert!(function.name.contains("bad")); diff --git a/nyth/archive.zip b/nyth/archive.zip index 126c318ce6aedca9878e069020b9fb81b24945af..84fc0c9e4db4f4dad83687b050ca59774cc7e7d4 100644 GIT binary patch delta 1435 zcmZWoT}V@57~a`2H$_ah`7?Jkx4Guf+?jJfZtgeJEU`9}lAw!hi$WUxs0BF!6_O}_ zKFXkr>Z-1?S{H%{i4j5<(cM6bey|KL)T=HG?fc%dbKKz!&fq-X^Stl#z3+2o#;BPw zs^H{tIwxO7ev!LV1-jg=aaDY49O*SWIR=iIGQiZpI=$R`3iVMnidi-t`x{NC?o%g< z=m+*Q=v%Q-v1tDiyW{$v%2(Z{fO${NF<-N3G^cK&Ck95*hPqxkm?(^o;0Hac(gHD{ zaYM;}(KJeGo&r%{ViOaIda%T=AnK+5{i*qqIx<<}O=K_1mPfjN{YL=(DcVAe2ALYqZKlgEvWOym~h&pN39_E)Bj z$5M5bUe4*oDwYS)BEu0Es=Q!e7aN#9@D9XF2MiKkUbR)g0@W{pw^?lyuv&c-uwlIs zus3)boJ~32EV21sZx`4*dK<7C4Tk_DlaCv?3iAf80$wm$HFzy6;pAzEN9Io{V`S$N z*R9vC_E~$n^JMf`QW1ZIAG}Ysk{=5?Z?t3?sgN)CqE|-l4m&tHzl;?$SfI^ZN{EF9 z^9YUGp|@tUf>ez-m8l=*ev00>d;>)+CdJL4yD`=$OATAh`#}A?fxEyqF1gZRqCifw zwaP40TGG3V6?&sl_7d>6STD5c!ENj|CI{yoMI076q+gASJoyB9?67Ac&~?+%NaNI? zNmnTfH0Q!6(CW0&_!GcbIhu6l0B^?0^BBn0y3<4B{G!he2F}}EZJ9dU`K&7-oRhp4 zxjTh-vcjbMh={-H4hiRkf3&$vcqjb1=EEYNgj4Bh&2UQ56JIX)81aOKxJ3oGHQwfH1hyDuQpz3|yG?|ZY{_Jt(6vwp7xoA>it29{hH zs3!@_U4@r(x@nUV}7b0KJ_aWaKCka4L(yfw~7 XBIM*Xq}7LzxD_dD3U>hIO8DS^HO~_6 delta 1423 zcmZWnOH30{6m9#EDWoyj7E0;xX-h2)pSF~KXlbWKq@aNOHAplOTG5y^7C+Gd8b0bq z1zsWx7k)OnFe(`qBuJvMae*f4%7qCUlM2cL-I-|6_uh2IPH1wQyz|aI=gxg+Zb~{g zB~`TV2N~HC{JYpUqtMC2!^$Wwc&|t+L(o1`CI`)&-2hSOfdkS~>Qe7~1QmNjdLRcp zyU-6m?=n>)C*O*lguxDPdk$2~4=CssUYnSK+3rLcSGOMrA1rLS)n)UJ{O}?-HUu z*DfaF?sK^T3U{MomvYNFn{Rn;q1&uEPP%6_j_uv+njqf|t@&?(IyMfaTBrP)L=nZs z2DH0mq`gz;BFw*WCA_JdB}10dHN7HE7NxpY*cSd>&0Wr(A#2# zgx(>rxAbRB~=DNdO0p7OHjaL*)C=D%w|p3_(wgRstcI{c0=##15EC1-mNq z1VDCh!EC0mYGc2G{bD{P1#d>;u)|`aHjn;_U~tr8-T{UNo8W`0N@{W?5xKIGU_Vxw zq@*3#%H(N7X|Z>kP4I@3wv&YS)8-Rw9CkVbclPFSq)I` z;j}vsGPRi``VB5SK(R+yKAd)C5Z;`N=g}lj8!oSGD~?6Ub=cjQlHsnOxEcN$r##2g z!XNRwjd}Kp*hx=_zsBsQ+BW`(^R!sI2cR&+_fAf`(A&UWYv3J!Cb6IJ)(e8zV9nbi z`YMd|__{@$*S>&=gRRQznp0MJg7&RCn}~pK$g zi7*n_fF0nlPP=LHtUH+soJq6-ZmuLa2xB1~JkRI=iXQ)ZI2A18Plre#mBH$i9QVjL wkkOzsB}tPkE(aX|Ws#R6QB;O}07X$4BNlS;J=p41NNj~=P4&%$a=L!^KV@O~tpET3