From 4a9cdcef2c65246c023c88e14d469912b86b9451 Mon Sep 17 00:00:00 2001 From: Simone Date: Wed, 9 Oct 2024 20:16:51 +0200 Subject: [PATCH 1/2] Add Chronicle unchecked price detector --- slither/detectors/all_detectors.py | 1 + .../statements/chronicle_unchecked_price.py | 147 ++++++++++++++++++ ..._8_20_chronicle_unchecked_price_sol__0.txt | 18 +++ .../0.8.20/chronicle_unchecked_price.sol | 119 ++++++++++++++ .../chronicle_unchecked_price.sol-0.8.20.zip | Bin 0 -> 8287 bytes tests/e2e/detectors/test_detectors.py | 5 + 6 files changed, 290 insertions(+) create mode 100644 slither/detectors/statements/chronicle_unchecked_price.py create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_ChronicleUncheckedPrice_0_8_20_chronicle_unchecked_price_sol__0.txt create mode 100644 tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol create mode 100644 tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol-0.8.20.zip diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 44a168c2b..d69b20820 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -97,5 +97,6 @@ from .statements.tautological_compare import TautologicalCompare from .statements.return_bomb import ReturnBomb from .functions.out_of_order_retryable import OutOfOrderRetryable +from .statements.chronicle_unchecked_price import ChronicleUncheckedPrice # from .statements.unused_import import UnusedImport diff --git a/slither/detectors/statements/chronicle_unchecked_price.py b/slither/detectors/statements/chronicle_unchecked_price.py new file mode 100644 index 000000000..47ad2ddc5 --- /dev/null +++ b/slither/detectors/statements/chronicle_unchecked_price.py @@ -0,0 +1,147 @@ +from typing import List + +from slither.detectors.abstract_detector import ( + AbstractDetector, + DetectorClassification, + DETECTOR_INFO, +) +from slither.utils.output import Output +from slither.slithir.operations import Binary, Assignment, Unpack, SolidityCall +from slither.core.variables import Variable +from slither.core.declarations.solidity_variables import SolidityFunction +from slither.core.cfg.node import Node + + +class ChronicleUncheckedPrice(AbstractDetector): + """ + Documentation: This detector finds calls to Chronicle oracle where the returned price is not checked + https://docs.chroniclelabs.org/Resources/FAQ/Oracles#how-do-i-check-if-an-oracle-becomes-inactive-gets-deprecated + """ + + ARGUMENT = "chronicle-unchecked-price" + HELP = "Detect when Chronicle price is not checked." + IMPACT = DetectorClassification.MEDIUM + CONFIDENCE = DetectorClassification.MEDIUM + + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#chronicle-unchecked-price" + + WIKI_TITLE = "Chronicle unchecked price" + WIKI_DESCRIPTION = "Chronicle oracle is used and the price returned is not checked to be valid. For more information https://docs.chroniclelabs.org/Resources/FAQ/Oracles#how-do-i-check-if-an-oracle-becomes-inactive-gets-deprecated." + + # region wiki_exploit_scenario + WIKI_EXPLOIT_SCENARIO = """ +```solidity +contract C { + IChronicle chronicle; + + constructor(address a) { + chronicle = IChronicle(a); + } + + function bad() public { + uint256 price = chronicle.read(); + } +``` +The `bad` function gets the price from Chronicle by calling the read function however it does not check if the price is valid.""" + # endregion wiki_exploit_scenario + + WIKI_RECOMMENDATION = "Validate that the price returned by the oracle is valid." + + def _var_is_checked(self, nodes: List[Node], var_to_check: Variable) -> bool: + visited = set() + checked = False + + while nodes: + if checked: + break + next_node = nodes[0] + nodes = nodes[1:] + + for node_ir in next_node.all_slithir_operations(): + if isinstance(node_ir, Binary) and var_to_check in node_ir.read: + checked = True + break + # This case is for tryRead and tryReadWithAge + # if the isValid boolean is checked inside a require(isValid) + if ( + isinstance(node_ir, SolidityCall) + and node_ir.function + in ( + SolidityFunction("require(bool)"), + SolidityFunction("require(bool,string)"), + SolidityFunction("require(bool,error)"), + ) + and var_to_check in node_ir.read + ): + checked = True + break + + if next_node not in visited: + visited.add(next_node) + for son in next_node.sons: + if son not in visited: + nodes.append(son) + return checked + + # pylint: disable=too-many-nested-blocks,too-many-branches + def _detect(self) -> List[Output]: + results: List[Output] = [] + + for contract in self.compilation_unit.contracts_derived: + for target_contract, ir in sorted( + contract.all_high_level_calls, + key=lambda x: (x[1].node.node_id, x[1].node.function.full_name), + ): + if target_contract.name in ("IScribe", "IChronicle") and ir.function_name in ( + "read", + "tryRead", + "readWithAge", + "tryReadWithAge", + "latestAnswer", + "latestRoundData", + ): + found = False + if ir.function_name in ("read", "latestAnswer"): + # We need to iterate the IRs as we are not always sure that the following IR is the assignment + # for example in case of type conversion it isn't + for node_ir in ir.node.irs: + if isinstance(node_ir, Assignment): + possible_unchecked_variable_ir = node_ir.lvalue + found = True + break + elif ir.function_name in ("readWithAge", "tryRead", "tryReadWithAge"): + # We are interested in the first item of the tuple + # readWithAge : value + # tryRead/tryReadWithAge : isValid + for node_ir in ir.node.irs: + if isinstance(node_ir, Unpack) and node_ir.index == 0: + possible_unchecked_variable_ir = node_ir.lvalue + found = True + break + elif ir.function_name == "latestRoundData": + found = False + for node_ir in ir.node.irs: + if isinstance(node_ir, Unpack) and node_ir.index == 1: + possible_unchecked_variable_ir = node_ir.lvalue + found = True + break + + # If we did not find the variable assignment we know it's not checked + checked = ( + self._var_is_checked(ir.node.sons, possible_unchecked_variable_ir) + if found + else False + ) + + if not checked: + info: DETECTOR_INFO = [ + "Chronicle price is not checked to be valid in ", + ir.node.function, + "\n\t- ", + ir.node, + "\n", + ] + res = self.generate_result(info) + results.append(res) + + return results diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ChronicleUncheckedPrice_0_8_20_chronicle_unchecked_price_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ChronicleUncheckedPrice_0_8_20_chronicle_unchecked_price_sol__0.txt new file mode 100644 index 000000000..6ddbfa4e5 --- /dev/null +++ b/tests/e2e/detectors/snapshots/detectors__detector_ChronicleUncheckedPrice_0_8_20_chronicle_unchecked_price_sol__0.txt @@ -0,0 +1,18 @@ +Chronicle price is not checked to be valid in C.bad2() (tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol#74-76) + - (price,None) = chronicle.readWithAge() (tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol#75) + +Chronicle price is not checked to be valid in C.bad() (tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol#65-67) + - price = chronicle.read() (tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol#66) + +Chronicle price is not checked to be valid in C.bad5() (tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol#101-103) + - price = scribe.latestAnswer() (tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol#102) + +Chronicle price is not checked to be valid in C.bad4() (tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol#92-94) + - (isValid,price,None) = chronicle.tryReadWithAge() (tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol#93) + +Chronicle price is not checked to be valid in C.bad3() (tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol#83-85) + - (isValid,price) = chronicle.tryRead() (tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol#84) + +Chronicle price is not checked to be valid in C.bad6() (tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol#110-112) + - (None,price,None,None,None) = scribe.latestRoundData() (tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol#111) + diff --git a/tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol b/tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol new file mode 100644 index 000000000..e12560fa7 --- /dev/null +++ b/tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol @@ -0,0 +1,119 @@ +interface IChronicle { + /// @notice Returns the oracle's current value. + /// @dev Reverts if no value set. + /// @return value The oracle's current value. + function read() external view returns (uint value); + + /// @notice Returns the oracle's current value and its age. + /// @dev Reverts if no value set. + /// @return value The oracle's current value. + /// @return age The value's age. + function readWithAge() external view returns (uint value, uint age); + + /// @notice Returns the oracle's current value. + /// @return isValid True if value exists, false otherwise. + /// @return value The oracle's current value if it exists, zero otherwise. + function tryRead() external view returns (bool isValid, uint value); + + /// @notice Returns the oracle's current value and its age. + /// @return isValid True if value exists, false otherwise. + /// @return value The oracle's current value if it exists, zero otherwise. + /// @return age The value's age if value exists, zero otherwise. + function tryReadWithAge() + external + view + returns (bool isValid, uint value, uint age); +} + +interface IScribe is IChronicle { + /// @notice Returns the oracle's latest value. + /// @dev Provides partial compatibility with Chainlink's + /// IAggregatorV3Interface. + /// @return roundId 1. + /// @return answer The oracle's latest value. + /// @return startedAt 0. + /// @return updatedAt The timestamp of oracle's latest update. + /// @return answeredInRound 1. + function latestRoundData() + external + view + returns ( + uint80 roundId, + int answer, + uint startedAt, + uint updatedAt, + uint80 answeredInRound + ); + + /// @notice Returns the oracle's latest value. + /// @dev Provides partial compatibility with Chainlink's + /// IAggregatorV3Interface. + /// @custom:deprecated See https://docs.chain.link/data-feeds/api-reference/#latestanswer. + /// @return answer The oracle's latest value. + function latestAnswer() external view returns (int); +} + +contract C { + IScribe scribe; + IChronicle chronicle; + + constructor(address a) { + scribe = IScribe(a); + chronicle = IChronicle(a); + } + + function bad() public { + uint256 price = chronicle.read(); + } + + function good() public { + uint256 price = chronicle.read(); + require(price != 0); + } + + function bad2() public { + (uint256 price,) = chronicle.readWithAge(); + } + + function good2() public { + (uint256 price,) = chronicle.readWithAge(); + require(price != 0); + } + + function bad3() public { + (bool isValid, uint256 price) = chronicle.tryRead(); + } + + function good3() public { + (bool isValid, uint256 price) = chronicle.tryRead(); + require(isValid); + } + + function bad4() public { + (bool isValid, uint256 price,) = chronicle.tryReadWithAge(); + } + + function good4() public { + (bool isValid, uint256 price,) = chronicle.tryReadWithAge(); + require(isValid); + } + + function bad5() public { + int256 price = scribe.latestAnswer(); + } + + function good5() public { + int256 price = scribe.latestAnswer(); + require(price != 0); + } + + function bad6() public { + (, int256 price,,,) = scribe.latestRoundData(); + } + + function good6() public { + (, int256 price,,,) = scribe.latestRoundData(); + require(price != 0); + } + +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol-0.8.20.zip b/tests/e2e/detectors/test_data/chronicle-unchecked-price/0.8.20/chronicle_unchecked_price.sol-0.8.20.zip new file mode 100644 index 0000000000000000000000000000000000000000..746efabf66030b2e0990d1243c6cd27d8dd38c32 GIT binary patch literal 8287 zcmb7~Lv$q!w5(5T+qSKaZL8BsI<{>m9osoEJGPyUZ5tR#J_Tcj}2@ZH(8l-73dqw?k+$_ z_`aDDHS8<4VU#Q#*CrK#=OiuSmme5PCQ#V(aUa0=$$vmzk*apiJCyP<=VJ0m+M=Oa zChWdz<6|y0IJK_ulc%Ej!>%uQagf2`NuYiV!PSVodYtmWTuwceBn6s=5qIi(>%U^I zihxRjULLVjW|1N@ox^Up1?nH8VfWBT?e-y|gAJlO8gw3qR$0#s=s5ZTsW%)PL9o|a zMc%gQw7P@}1o!BN;Su;HV@CwCT71WHaYXqoJ@W)IKBI!H`b*xxvwWN_iL|9t7)#jU znOBOgn7?Ko^X)$@Xy}se7WKGrB`5I|A!}p1Fd8gtfM@P?*V3j_n^tM^bB_JY5*hU&UaU#jY)`8{J-r+ zTMjmiLd~(-sqWu|K6#T|mJjI~W$TY&lgmYwiQ})usiw*|880&!$9knkS0^Xyo+SFe zol`g+E>+D4pFfE&E5M6^l_+X66aA{1CzR6F-VOaowTyq}WXC4%u|K&`Xpb=Kzj5>3 zPFxXh+Z1W>_@4h!SwLp+w-}?=o zL5lqcwkYIf?T~dzSGY5h$~X&`B8Gd1LiI*t9f6o!53?~f__VO z+$4=^Lfmov%_LD(lFr|?o;SY>u5k#J$$@c|^3Zjfs*UD}RKZ?qEL82b&2ppW(u232 zrAF-G@L`sIT()L*L8~{1>K%8RDytpuKVn&S>kix+bx#PtvZ~C2XkdE-HWNA$$>=q*A#}%d`zNCbxTp(AL_0|L zn3cuKtKQn;Kbg`Q?+>qs*sw1-LL7?Z)ETkgw%WioPu3VDAZGubp_cdubztUIb@}t+ z7v^BvjTPcfJvpnP={kBCb9rb?&q1ast@&+Rf#LL7l5yEWtk=A#RMRO=T5RhPm21x$ zrEI)FjxSh(c8%L8yWAjO>DgAhUErza62+x@ zzoSo$7B&YNT3K^s#GN-RoGrrqQ<{8r>$o^$82E?oeQjo3J@EFe4%_#7x}%XW;LH? z2@m<0m7OuM#U5;@BJDho+WFLMfa_zuT4&SUhYeIl2=Jnt(G=@*j*`7qX~X^}LfdjZ zH2P(VQLp=Gb?|1#-JA|c*kge;a2Yizeh&X}oY~C)#^eG+W`rTMF_0Pr?<+fU*%zP> zPQkcQZx$QXXc?`h=w;WEm;s6O3ii25K{uy?U~&y+z6!y{M3xNYVOls9H% zwVtHZV;eqt%x^pvzb(5jXV8pFt$FyG`-oTd9`f2gH+k<6#R#Ldp%SonDJPH#+$O67eFe!E^_dhZ+z=&=*B)K=53PN%#2+#dz%SSuwkUs`f*;LYgqX@a4A9Z z%-Ug%JcI74Dl$%&9TkMKhjxW2dyJXg{wJxNd4NAEBy$>7!_-}zr|6N{!^%XLDGT4e z-TAU6Lm&Rmk}2#I{|loMvtt-9xEaYE(RL706pdDfb+jmInmQ`_h@6vOrc%eB;&qcU)n$d_tg!SJR!AI<_wDH6)%He z4MZM0Y;jZg%bqPcx}%0aZNGmnIW#aJ={VpnF7_T57iGJJ4#`F+i~q)U$Xb8vh~1i| z(deTkQ902%XGLwCHDep5j7oAoAIJ|3GeR6UAA(T9r)iw7=`I)nu=WVp6>%@!M{@}L zDfBdt9>cZ=?)UDJZ0M`jwPxp}pm zg4W-|Ft*itpOc?bl)3)J7-QuTVwwEigvL)V9=g1&X|&k-<*7?z*3Q_)s1!sl3%_qp z&YS2 z+KY@8f0Rnk{)Jz~w#7sw%Sc|TrR`T&gOx*#(%hXNh^0p@G(&f>>p2aN@O4Y#G7s_} z_j`w~Rc)()1s1CIh%WdQmda)=L$?ifZP06O6uqPtQXDn0!Ch_48;Qa?-B4bfL~0x? z&ZRhwj#$U+=n>AkRJbGnts-|Pxj;Fpsl8b2hvYJwJ6s?W(kYA7DUn^3PeOs9MbZ@aMPH`}KM zqsSfQq2^S{Z7H8NLD?>Td5DVhi4MZ!HrND5ZA8yGf{uJMIns}z_ixvA}k%C7hZ&0M+27L+8zDDS>R&$Qg5LVsvm~`VzinDVHVg{%~3Bou{Jkl zX+c9hIkI4(Rgjo!9y|M;+9TuIJ_6Av|5~Rkl;mBTKyXdI1lB90-9bjt5jcw(V0n+b zj?<-8BE%RE1>!XmgyqPfjN@6|hMDGu4^1h-H3}SxBuVV_VqMkK&Nf0m=SpU3V-jNQffqaX7n#FEZQQ@_K7aXZDs!?wuYMg(B)#bS%*ofZDoFYjyI|VBlg$9 z`OiieGPt07k|~)QGmLSY@5@27YktJwx>!Z(b=05LbNw5fltS?U(0gP3#FZq z5y`CH5$!~+YMJg>kCRK~T46!wSbsdj)`+Ha*V%?*;=!4imD#KP!g5R|i-mGMHOnDpR5qs|P)5eidVMO`=g&F_l$n1j=gpU|l+ zY>UNF??bKDsb?N^{RyWK9Xs0>9<$UN+&p+I449{~7(2jWh1UF07f+!CP5y{(E%~Et zBjxIPNqi-{taiA~|1Dd1YW}C*q0fxy)>L%)5H1qt;@jayNh5>jvD<19vxPsufUj0O zSx;VdSzo_u#p@@RbCaC*CniniWH|koPsl)7~8^WM%o`fr|!p?h*szEF3?=`OI-o<8*0;FFaGip1gEjf9=`_h zS>57d*nD2vDAfx;6Q{i60F%}m5r00a^3FDB1txg5-#BNh-B$sboz*mLWAUTbYkgFp)p4Dl?ioO*fP z#X?;a4gWASQp%W0YG&LWDU+rkZe_K)OG zk-@VM-Wa$XtIuxr-1@1FWk6$P9D=~-h*PFc{9Ec{H&&Y<5^n1d>bp4c@VCeAA@FII z%ajSX1DX(F$r*afR>9bZU6sGE>=d`QLQDF-g)NdzHt;+KfCI9o#8rIeF(}6Q_}Vi< zBg=NNQD>BGf5NR)exSAfrBFZ80>d{uhfRvuzbG_HK4&G=uJ8k|Q;Ewsr)OdkX=cXq|h^JrQSq$S7jJ zBKf15?OXf)2zrXhHO&JV^(qTL*|^P7@WOllxRj}o?=3~;mt2l zDG%33o7_Ljma5|)=u2x~Y7Ot(!lCU3lmaUf_o}!|WnZHDv~CiXs24qc0NERnQ~&Cu zTa|Ou^Y~pmMjQz_E6+}s{xWt0Ng5d|dBLN&Jd8qbQ$kBUd%8QToWZ6o;|06Yy-{Vp z`Yr5UI`H4XDmhHSN$y8L+@~R^xInyj%-Cb29tKTUM|uexEPfhy%}Gld`fy%s(CHmV zbD94;P>29QjI{u5F87vB^mL@SR{eTg!uE?tpf5f(2ESsPD-Hr2*cGT+`0K<(Ma0sGEX2NUejzlkc12AF2zP_9du5^Ou20YTzv{RQv0jaJw zHsK5k-zJ6z=2SOigFj2VW(rgx8&@Ck=4LH;e(c2}Ae9SkZ4uqw4<01~ee(}PQ&=OW zn{@tM$(fdEZQ}!RaB$(S^8<`Cq#vpwsPxy(X&4(lsjY~{s+u?8@q*36io6j}cKBXNXTh$ML&Gm0AhLCuh|J(7QPW)svoI7Ogl8LjMJa z?0b~8#L0ArC-ri~iHMhZ;SgfF3BBn=L=laEw!j<4t$*AAlmP6{R%40H-m(#YV$-@kpf)PCdoP!tmH=b7HaeK zHU@t$pRW7ng!O1scv8=7(z_0+SXQ~t2|nw53{LvKaC|0;%+qY8pZ2f{*4|VS&V6^G znp4`nKShWx`Oj)?^`(RGfQ=N%C5j-`wV~&x^mzpAr*6nhqednAA!Q?KFEYYxgSx%9 z&BJ;tKW6?81k!uAdBYJI_AS^&dieI$=6IJHaM^&d4o}7Oq4c=N2X6T?H0z$wRU#5I zkjOKK=(gPM1iR^0v?g_A=W&VbkMDO|p-#;cWp@OHpL#p}bTZi8S#(g#AXxDMMHUlT zERDOx`|!hp2W!JD;-H|8WDld?x~Afdi~0KG|4mKLEgcyW|H3!LCRi>u2pUCn zw!)liHEG%8@mHkduwO$|zBMtw2*{7K@#GJ`Cl4-^QtaxFfo9trxAtbLRC$qWzYDE= z%_ex`yw1v9_SlPeAYYg~fPmTBs5QayopajCZKBn&H;R%DE!OLT|Kh$6eX?CGUtb$v zSdbVvzV$|?1dhTOe4U)9dre|KS;eCpjuudJ)!zj4ccpx;A@IEZ5c>nsn@6I#v-yi9 z6rn=TQOsG1@`#8XEcO7FqKz2cJizFt7 zkxlZ&1gd^ik}=4M)}jouBQu9?qT@wm`tHD}LQq>Ji#L%#E1E6gY)CE8Panl(I#1Ju z54Y@|ufIrC0HJuS=J1O`p;Ph-$y7zk8a>F@2hz;4FsYjdUwWDKw(DWytwY!y`JwHh z_I0<;XpM*6Sb{xbjZlvrg1Sm;_Yic{Pxp&%K4BK}+Wh;^BJ}&upfJ!Vxvn6qXMwv2 zbq3@D?~wBb3IZ?h1KC1LmFfi-MSQR=VP-9i=eaLGu%l*>`YL8+)8<4(*63)&=NjnW zCtcvM%JCLAdL=aS-X#)jGhVOl+@s6vul+N?Z#jRuf0nCKu?1lUCJjIDvA_N49dYQj zG7)nberKs$u(!N%l7IWR9RWTUmP2Cm4A$L~N|?4aTV?ltPKcbl_zAN>(@kqTXN%6R zUhUr5s;_-2#_DxF;v4S3$>^t_e_-FKktJqyoL{i78)6ZIozIW-NLUQ)X=H#%79rhy z)4As!T4(MhbXwB5m{Dw8!73-6@$vSsB$F-p{@Uv3F5~u{xBU1HZ)Hh>&B;$o5?y+L zc2*-vCeus?;Eo?CJu9J1zkGQV*oc0hk>?eKpQ)3puo5@18SU(G7mXd=*w+5(9s)__ z+aM_}!*B*?{&|=z&7b^p+-AIbnAdf}NsgB|KEj;CKNwG*kK$!1litFMEB4k(4!l=zK@A>1qTs zZ!2li8aFdfi34wesoKuFlMm^sPh=9VJ8arHd;J7x4j@Kp(39{=JeunB%FI|}~l}Q}3Y)Jg=OGYQjPWm?~NzVo@ zkvxtB5;*?2X~!+;&r1gTIps_VHPc*p$V|;B(7*u0tdk#~giHCg?T$=!zLODN6zJqL1h@%aRbeCJH{&d-yYB*5jLfgm(z~@Ej#`U>AzAX< z;|!uLB0?`v>JKg88?VQN>#2H4wc~Mb5{4+u(_^p2aLn`(nL^CP4Og&}jDPfcmbj|a zs8RCAB`?P$rs`9{GhQabM%%JTQkvvyg08+<69ZrwvD%&dc!g0;+nQzKY7*CdjxUvw zC;gj)qqLBwP$ZsHD66TyWRbG^1zUkYJ-bzGV62Mjo=yqCV_e9vY`!Vjq)(17kMAg; zi{z?70`}YcT#fgDo`Ou}-MEmSSfiA_&q@kR=1N~ea2KY(1TeoO7Ed3Z9EYQ$P$Jo- zd|^yOe^Hlf?% zzgE>u$KLUZ&WO%~G)F<0y?oh+Dr_8BCiP$j-kHnUVb< z_JtrBtg{1pukLUh8wpL-56!?UVfDa=Rj23H=yl#BYv|m%blI9)CUC5K-*ANSuLP?lPovd}meA_BK966^B}}noxDvHi`HV?vEru&>efL!`9P@%9;-elg zZ23iy`&u)x>bTy~9`B=^UW5Fb+Qt955(P!#GLgVb2NzgoJ53fq8t(Yh)**>DVE3+H4%0HTik>6nk%Bk&YF+I?((ur3~ZQ=k#wD3UBP9@{F ztXL;k2sUU3^=KPtWgDE8$yZ~qpCXJ?@b5K|w$igQpUjs%+<1~hh7=PP8Hms-N3~K# zQGstlxYDju?>UvsnFaeIT&(%Cl!d4smCAqHsfmtrN|6br5+5tV_zI7>(P_m^vL-Q^ zI+pM$+Cs2*saLt{$78|M%>L|V{d4asw??b&4;L&okc#~@k%g*0=dU_cUfUXfGAXcg z7Kbcd5N-Gj5&kB^q*~5AKI@g6F8C1)IG$hj;H~izi2XG@V6wujR#Xha;>-y%RGysj zug2X*GICfL)e4U9zCo3h9T5{UIm2;`t*O`&R9O`NEq_e288c8(EOacVmdP!I^^<<7 z5!oHy1MDbq&A~^omCfq2q#B-UN?us}3l8K%` zbgi;8Jz7x%<-R3LH?;U|O_>?blkV7195g6mQD+2c-Oyz7wrcQ?&m)!D-|5K`!*A%A z;cY^S5*g!53N&Tnh8MJn>@CsUu1Ib(y(Ky-k)u0bG`!D65E}T{Rm4O5bOm(0a+cNk|F3 zfrYnp9eR(@WrN30cpbeIWRWtAA*RZp5L!z?ofpmGvmM$rJi5Kl_Buak`F_HQ66PY7 zv*N>ZmFf>_HG<>jX+7>xNBOEM!jdkN91$dFYkT@cz$mh_KBPUey^$2=)DX~s9L0Gh z0=4qrC_(5*DNX_cA$qrF5?l9KsAZFmo-oSZMkk&oS$DDDz7a9C@xmR=yS=Ux* z#}Y0-^w*p#@Pj6_RU=6LJUpvA#3L7}3|FAP4J=F+vmpmuHJkIFwfMwBR0#|zJt&qQ z%ExD-yfIuu9?R{=PbzbpJ3)Y~c@uo?mK%S&!NK4y{i=dklGiIM%)X>J^Zh4K5A|4l zeYBCi?y*p12W1gaJN!q2xjn%0RNm<{)woDY{~^Iz>?^%p#v+)GK8%gQDgS4lk6$(n zW|!VA2)pD&4X20|`xPd_NOL1E8(T!nlWt zRy4$XD2rLrj%o0G#nCpD#P3by7}5w3tPcx|smtkF8mSPUXf|J<^Egxf!`dRniWH^? zu7k=Gkoe;qQM_xGo8;Uu0K;2O1cV|z?i8Gcq=vJVna1&rAI(*rX2_R_WbkHGC7OGY zaBqoR*ni{tM*WJD^E;)KEXAq5XLDV85yI%WRSM6l{e zq4C!nf%Tx8A_Sx;)c?-<|HJ+N|AHa@$NtacUriAP_J4v9|H=42zxeNs008)Zh862s literal 0 HcmV?d00001 diff --git a/tests/e2e/detectors/test_detectors.py b/tests/e2e/detectors/test_detectors.py index 2c6a5f55a..768ccb3f2 100644 --- a/tests/e2e/detectors/test_detectors.py +++ b/tests/e2e/detectors/test_detectors.py @@ -1714,6 +1714,11 @@ def id_test(test_item: Test): "out_of_order_retryable.sol", "0.8.20", ), + Test( + all_detectors.ChronicleUncheckedPrice, + "chronicle_unchecked_price.sol", + "0.8.20", + ), # Test( # all_detectors.UnusedImport, # "ConstantContractLevelUsedInContractTest.sol", From 9c9818c24df73c20560b29dcbbfa7a311d051f8e Mon Sep 17 00:00:00 2001 From: Simone <79767264+smonicas@users.noreply.github.com> Date: Wed, 9 Oct 2024 20:19:13 +0200 Subject: [PATCH 2/2] Update tests/e2e/detectors/test_detectors.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- tests/e2e/detectors/test_detectors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/detectors/test_detectors.py b/tests/e2e/detectors/test_detectors.py index 768ccb3f2..b42af234f 100644 --- a/tests/e2e/detectors/test_detectors.py +++ b/tests/e2e/detectors/test_detectors.py @@ -1718,7 +1718,7 @@ def id_test(test_item: Test): all_detectors.ChronicleUncheckedPrice, "chronicle_unchecked_price.sol", "0.8.20", - ), + ), # Test( # all_detectors.UnusedImport, # "ConstantContractLevelUsedInContractTest.sol",