diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 4151759f0..110339aeb 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -98,3 +98,19 @@ from .statements.tautological_compare import TautologicalCompare from .statements.return_bomb import ReturnBomb from .functions.out_of_order_retryable import OutOfOrderRetryable +from .defi.price_manipulation_high import PriceManipulation +from .defi.price_manipulation_low import PriceManipulationLow +from .defi.price_manipulation_medium import PriceManipulationMedium +from .defi.price_manipulation_info import PriceManipulationInfo +from .defi.k_value_error import KValueError +from .defi.defi_action_nested import DeFiActionNested +from .functions.centralized_info import CentralizedRiskInfo +from .functions.centralized_low import CentralizedRiskLOW +from .functions.centralized_medium import CentralizedRiskMEDIUM +from .functions.centralized_other import CentralizedRiskOther +from .functions.centralized_init_supply import CentralizedInitSupply +from .functions.transaction_order_dependency_high import TransactionOrderDependencyHigh +from .functions.transaction_order_dependency_low import TransactionOrderDependencyLow +from .functions.transaction_order_dependency_medium import TransactionOrderDependencyMedium + + diff --git a/slither/detectors/defi/__init__.py b/slither/detectors/defi/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/detectors/defi/defi_action_nested.py b/slither/detectors/defi/defi_action_nested.py new file mode 100644 index 000000000..8905a0e4a --- /dev/null +++ b/slither/detectors/defi/defi_action_nested.py @@ -0,0 +1,328 @@ +from typing import List +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output +from slither.core.declarations import Function, Contract +from slither.core.variables.state_variable import StateVariable +from slither.core.declarations import FunctionContract, SolidityVariableComposed, Modifier +from slither.core.cfg.node import NodeType, Node +from slither.core.solidity_types import ArrayType +from slither.analyses.data_dependency.data_dependency import is_dependent +from slither.core.declarations.solidity_variables import ( + SolidityVariable, + SolidityVariableComposed, + SolidityFunction, +) +from slither.core.variables.local_variable import LocalVariable + +from slither.core.expressions.assignment_operation import AssignmentOperation + +import difflib +from slither.core.declarations import Contract, Function, SolidityVariableComposed + +from slither.core.expressions import CallExpression,TypeConversion,Identifier + +from slither.slithir.operations import ( + Assignment, + Binary, + BinaryType, + HighLevelCall, + SolidityCall, + LibraryCall, + Index, +) +from slither.visitors.expression.export_values import ExportValues +from slither.core.solidity_types import MappingType, ElementaryType +class DeFiActionNested(AbstractDetector): + """ + Detect when `msg.sender` is not used as `from` in transferFrom along with the use of permit. + """ + + ARGUMENT = "defi-action-nested" + HELP = "transferFrom uses arbitrary from with permit" + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.MEDIUM + + WIKI = "http://" + + WIKI_TITLE = "DeFi Action Nesting" + WIKI_DESCRIPTION = ( + "This detector identifies instances where DeFi actions are nested, specifically focusing on scenarios where `msg.sender` is not used as `from` in transferFrom while also utilizing a permit function." + ) + WIKI_EXPLOIT_SCENARIO = """ + }""" + ERC20_FUNCTION = [ + "transferFrom", + "safeTransferFrom", + "mint", + "burn", + "burnFrom", + "approve", + "balanceOf", + "totalSupply", + "transfer", + "allowance", + "safeTransfer", + "safeApprove", + "getReserve", + "transfer", + "balance" +] + UNISWAP_FUNCTION=[ + "_addLiquidity", + "addLiquidity", + "addLiquidityETH", + "removeLiquidity", + "removeLiquidityETH", + "removeLiquidityWithPermit", + "removeLiquidityETHWithPermit", + "removeLiquidityETHSupportingFeeOnTransferTokens", + "removeLiquidityETHWithPermitSupportingFeeOnTransferTokens", + "swapExactTokensForTokens", + "swapTokensForExactTokens", + "swapExactETHForTokens", + "swapTokensForExactETH", + "swapExactTokensForETH", + "swapETHForExactTokens", + "swapExactTokensForTokensSupportingFeeOnTransferTokens", + "swapExactETHForTokensSupportingFeeOnTransferTokens", + "swapExactTokensForETHSupportingFeeOnTransferTokens", + "swap" + ] + depositTransferMatchScore=0.1 + depositAssignementToUserScore=0.1 + withdrawTransferMatchScore=0.1 + withdrawAssignementToUserScore=0.1 + defaultDependencyScore=0.1 + lendingTransferMatchScore=0.1 + lendingAssignementToUserScore=0.1 + liquidateTransferToScore=0.05 + liquidateTransferFromScore=0.05 + def checkIfHavePriceManipulation(self,contract:Contract): + result=[] + if contract.is_interface: + return result + for function in contract.functions: + if function.view: + continue + name,score=self._check_function_action_type(function) + if_has,node,info,func=self._check_defi_action_nesting(function) + if name!="unknown" and if_has and \ + name!=info and \ + function.name not in str(func).lower(): + result.append([function,node,info,func]) + + return result + + # Check defi function nesting + def _check_defi_action_nesting(self,func): + if self._check_func_if_have_uniswap(func)[0]: + return True,self._check_func_if_have_uniswap(func)[1],"UNISWAP ACTION",self._check_func_if_have_uniswap(func)[2] + for node in func.nodes: + for call in node.calls_as_expression: + if call.called and hasattr(call.called,"value"): + if self._check_function_action_type(call.called.value)[0]!="unknown": + return True,node,self._check_function_action_type(call.called.value)[0],call.called.value + return False,"unknown","unknown","unknown" + + # Check whether func contains uniswap related (self, call): + def _check_func_if_have_uniswap(self,func): + for node in func.nodes: + for call in node.calls_as_expression: + if call.called and hasattr(call.called,"member_name") and call.called.member_name in self.UNISWAP_FUNCTION: + return True,node,call.called.member_name + return False,"","" + + # Check what function a function is and return the corresponding name + def _check_function_action_type(self,func): + list_action=["deposit","lending","withdraw","liquidate","unknown"] + depositDiffscore,depositTransferScore,depositAssignmentScore,depositDependencyScore=self._check_function_if_staking_or_deposit_or_collateral(func) + lendingDiffscore,lendingTransferScore,lendingAssignmentScore,lendingDependencyScore=self._check_function_if_lending_or_borrow(func) + withdrawDiffscore,withdrawTransferScore,withdrawAssignmentScore,withdrawDependencyScore=self._check_function_if_withdraw_or_unstake(func) + liquidateDiffscore,liquidateTransferScore,liquidateAssignmentScore,liquidateDependencyScore=self._check_function_if_liquidate(func) + TransferScoreList=[depositTransferScore,lendingTransferScore,withdrawTransferScore,liquidateTransferScore] + + list_diffscore=[depositDiffscore,lendingDiffscore,withdrawDiffscore,liquidateDiffscore] + list_allscore=[ + depositDiffscore+depositTransferScore+depositAssignmentScore+depositDependencyScore, + lendingDiffscore+lendingTransferScore+lendingAssignmentScore+lendingDependencyScore, + withdrawDiffscore+withdrawTransferScore+withdrawAssignmentScore+withdrawDependencyScore, + liquidateDiffscore,liquidateTransferScore+liquidateAssignmentScore+liquidateDependencyScore + ]# The lower the score, the more output. The reason is that the lower the score, the more functions are matched. At the same time, there must be a transfer behavior. + if max(list_diffscore)>0.6 and TransferScoreList[list_diffscore.index(max(list_diffscore))]!=0: + return list_action[list_diffscore.index(max(list_diffscore))],max(list_diffscore) + elif max(list_allscore)>0.4 and TransferScoreList[list_allscore.index(max(list_allscore))]!=0: + return list_action[list_allscore.index(max(list_allscore))],max(list_allscore) + else: + return list_action[4],max(list_allscore) + + # Checks if a call is an erc20 transfer call from the user, returns yes/no and the call itself + def _check_call_if_transfer_from_user(self,call,func): + if call.called and hasattr(call.called,"member_name") and call.called.member_name in ["safeTransferFrom","transferFrom"]: + # The first transfer parameter is msg.sender or the external address passed in + if (isinstance(call.arguments[0],TypeConversion) and hasattr(call.arguments[0].expression,"value") and isinstance(call.arguments[0].expression.value,SolidityVariable) and call.arguments[0].expression.value.name=="msg.sender") or \ + (isinstance(call.arguments[0],Identifier) and isinstance(call.arguments[0].value,SolidityVariableComposed) and call.arguments[0].value.name=="msg.sender") or \ + (isinstance(call.arguments[0],Identifier) and call.arguments[0].value in func.parameters): + return True,call.arguments + + def _check_call_if_transfer_to_user(self, call, func): + if call.called and hasattr(call.called, "member_name") and call.called.member_name in ["safeTransfer", "transfer"]: + # The first transfer parameter is msg.sender or an externally passed address + if (isinstance(call.arguments[0], TypeConversion) and hasattr(call.arguments[0].expression, 'value') and isinstance(call.arguments[0].expression.value, SolidityVariable) and call.arguments[0].expression.value.name == "msg.sender") or \ + (isinstance(call.arguments[0], Identifier) and isinstance(call.arguments[0].value, SolidityVariableComposed) and call.arguments[0].value.name == "msg.sender") or \ + (isinstance(call.arguments[0], Identifier) and call.arguments[0].value in func.parameters): + return True, call.arguments + + def _check_node_if_mapping_assignment_with_sender_or_param(self, node, func): + if isinstance(node.expression, AssignmentOperation): + for ir in node.irs: + if hasattr(ir, "variables") and any(isinstance(e.type, MappingType) for e in ir.variables): + if (hasattr(ir.expression.expression_right, "value")) and ( + (isinstance(ir.expression.expression_right.value, SolidityVariableComposed) and ir.expression.expression_right.value.name == "msg.sender") or \ + ir.expression.expression_right.value in func.parameters and isinstance(ir.expression.expression_right.value, LocalVariable) and ir.expression.expression_right.value.type.type == "address"): + if hasattr(node, "variables_read") and len(node.variables_read) > 0: + return True, set(node.variables_read) - set(ir.variables) + + def _check_function_if_staking_or_deposit_or_collateral(self, func: FunctionContract): + depositDiffScore = difflib.SequenceMatcher(a="deposit", b=func.name.lower()).ratio() + stakeDiffScore = difflib.SequenceMatcher(a="stake", b=func.name.lower()).ratio() + diffscore = depositDiffScore if depositDiffScore > stakeDiffScore else stakeDiffScore + + transferMatchScore = 0 + assignementToUserScore = 0 + dependencyScore = 0 + call_arguments = [] + var_read = [] + if hasattr(func, "nodes"): + for node in func.nodes: + for call in node.calls_as_expression: + # Check if there is an ERC20 transfer function to a user + transfer_ret = self._check_call_if_transfer_from_user(call, func) + if transfer_ret is not None and transfer_ret[0]: + call_arguments.append(transfer_ret[1]) + transferMatchScore = self.depositTransferMatchScore + # Check for array operations, and whether the parameter is msg.sender or an address type parameter in func + assin_ret = self._check_node_if_mapping_assignment_with_sender_or_param(node, func) + if assin_ret is not None and assin_ret[0]: + var_read.append(assin_ret[1]) + assignementToUserScore = self.depositAssignementToUserScore + # There is data dependency between array operations and transfers + if self._check_two_arguments_if_dependent(call_arguments, var_read, func): + dependencyScore = self.defaultDependencyScore + + return diffscore, transferMatchScore, assignementToUserScore, dependencyScore + + def _check_function_if_withdraw_or_unstake(self, func: FunctionContract): + withdrawDiffScore = difflib.SequenceMatcher(a="withdraw", b=func.name.lower()).ratio() + unstakeDiffScore = difflib.SequenceMatcher(a="unstake", b=func.name.lower()).ratio() + diffscore = withdrawDiffScore if withdrawDiffScore > unstakeDiffScore else unstakeDiffScore + + transferMatchScore = 0 + assignementToUserScore = 0 + call_arguments = [] + var_read = [] + dependencyScore = 0 + if hasattr(func, "nodes"): + for node in func.nodes: + for call in node.calls_as_expression: + # Check if there is an ERC20 transfer function to a user + transfer_ret = self._check_call_if_transfer_to_user(call, func) + if transfer_ret is not None and transfer_ret[0]: + call_arguments.append(transfer_ret[1]) + transferMatchScore = self.withdrawTransferMatchScore + # Check for array operations, and whether the parameter is msg.sender or an address type parameter in func + assin_ret = self._check_node_if_mapping_assignment_with_sender_or_param(node, func) + if assin_ret is not None and assin_ret[0]: + var_read.append(assin_ret[1]) + assignementToUserScore = self.withdrawAssignementToUserScore + # There is data dependency between array operations and transfers + if self._check_two_arguments_if_dependent(call_arguments, var_read, func): + dependencyScore = self.defaultDependencyScore + return diffscore, transferMatchScore, assignementToUserScore, dependencyScore + + def _check_function_if_lending_or_borrow(self, func: FunctionContract): + lendingDiffScore = difflib.SequenceMatcher(a="lending", b=func.name.lower()).ratio() + borrowDiffScore = difflib.SequenceMatcher(a="borrow", b=func.name.lower()).ratio() + diffscore = lendingDiffScore if lendingDiffScore > borrowDiffScore else borrowDiffScore + + transferMatchScore = 0 + assignementToUserScore = 0 + call_arguments = [] + var_read = [] + dependencyScore = 0 + if hasattr(func, "nodes"): + for node in func.nodes: + for call in node.calls_as_expression: + # Check if there is an ERC20 transfer function to a user + transfer_ret = self._check_call_if_transfer_to_user(call, func) + if transfer_ret is not None and transfer_ret[0]: + call_arguments.append(transfer_ret[1]) + transferMatchScore = self.lendingTransferMatchScore + # Check for array operations, and whether the parameter is msg.sender or an address type parameter in func + assin_ret = self._check_node_if_mapping_assignment_with_sender_or_param(node, func) + if assin_ret is not None and assin_ret[0]: + var_read.append(assin_ret[1]) + assignementToUserScore = self.lendingAssignementToUserScore + # There is data dependency between array operations and transfers + if self._check_two_arguments_if_dependent(call_arguments, var_read, func): + dependencyScore = self.defaultDependencyScore + return diffscore, transferMatchScore, assignementToUserScore, dependencyScore + + def _check_function_if_liquidate(self, func: FunctionContract): + liquidateDiffScore = difflib.SequenceMatcher(a="liquidate", b=func.name.lower()).ratio() + diffscore = liquidateDiffScore + + transferToScore = 0 + transferFromScore = 0 + + transferMatchScore = 0 + assignementToUserScore = 0 + call_arguments = [] + var_read = [] + dependencyScore = 0 + if hasattr(func, "nodes"): + for node in func.nodes: + for call in node.calls_as_expression: + # Check if there is an ERC20 transfer function to a user + transfer_to_ret = self._check_call_if_transfer_to_user(call, func) + transfer_from_ret = self._check_call_if_transfer_from_user(call, func) + if transfer_to_ret is not None and transfer_to_ret[0]: + call_arguments.append(transfer_to_ret[1]) + transferToScore = self.liquidateTransferToScore + if transfer_from_ret is not None and transfer_from_ret[0]: + call_arguments.append(transfer_from_ret[1]) + transferFromScore = self.liquidateTransferFromScore + + # Check for array operations, and whether the parameter is msg.sender or an address type parameter in func + assin_ret = self._check_node_if_mapping_assignment_with_sender_or_param(node, func) + if assin_ret is not None and assin_ret[0]: + var_read.append(assin_ret[1]) + assignementToUserScore = self.lendingAssignementToUserScore + # There is data dependency between array operations and transfers + if self._check_two_arguments_if_dependent(call_arguments, var_read, func): + dependencyScore = self.defaultDependencyScore + return diffscore, transferToScore + transferFromScore, assignementToUserScore, dependencyScore + + def _check_two_arguments_if_dependent(self, callArgs, assignArgs, func): + args = [arg for callArg in callArgs for arg in callArg if isinstance(arg, Identifier)] + for arg in args: + if any(var for vars in assignArgs for var in vars if is_dependent(arg.value, var, func)): + return True + + def _detect(self) -> List[Output]: + """""" + results: List[Output] = [] + detection_result = [] + for c in self.contracts: + detection_result = self.checkIfHavePriceManipulation(c) + # print("risk in",function.name,"with potential",info,"function:",func) + for data in detection_result: + + info = [ + data[1], + " is a potential nested defi action in its father defi action which has the risk of indirectly generating arbitrage space", + "\n", + ] + res = self.generate_result(info) + results.append(res) + + return results diff --git a/slither/detectors/defi/k_value_error.py b/slither/detectors/defi/k_value_error.py new file mode 100644 index 000000000..9ddab95c5 --- /dev/null +++ b/slither/detectors/defi/k_value_error.py @@ -0,0 +1,62 @@ +import re +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification + + +class AMMKValueError(AbstractDetector): + """ + Module detecting potential errors in K-value calculation in Automated Market Makers (AMMs). + """ + + ARGUMENT = "amm-k-value-error" + HELP = "Potential K-value calculation errors" + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.HIGH + + WIKI = " " + + WIKI_TITLE = "AMM K-Value Error" + WIKI_DESCRIPTION = ( + "" + ) + + # region wiki_exploit_scenario + WIKI_EXPLOIT_SCENARIO = """""" + WIKI_RECOMMENDATION = "Ensure correct calculation of the K-value in Automated Market Makers (AMMs) to avoid potential trading issues." + + def _detect(self): + """ + Detect multiple constructor schemes in the same contract + :return: Returns a list of contract JSON result, where each result contains all constructor definitions. + """ + results = [] + matches0="" + matches1="" + matches2="" + tainted_function_name="" + tainted_nodes=[] + + for contract in self.contracts: + # check if uniswap + if "pair" in contract.name.lower() and any("swap" == f.name for f in contract.functions) and any("burn" == f.name for f in contract.functions) and any("mint" == f.name for f in contract.functions): + print("found function") + for f in contract.functions: + if f.name=="swap": + print("found swap") + tainted_function_name=f.name + for node in f.nodes: + pattern = r'10+' + if "balance0.mul(" in str(node): + matches0 = re.findall(pattern, str(node)) + tainted_nodes.append(node) + if "balance1.mul(" in str(node): + matches1 = re.findall(pattern, str(node)) + tainted_nodes.append(node) + if "require" in str(node) and ">=" in str(node): + matches2 = re.findall(pattern, str(node)) + tainted_nodes.append(node) + if matches2!=matches0 or matches2!=matches1: + info = [tainted_function_name, " has potential K Value Error in :\n",tainted_nodes[0],tainted_nodes[1],tainted_nodes[2]] + res = self.generate_result(info) + results.append(res) + + return results diff --git a/slither/detectors/defi/price_manipulation_high.py b/slither/detectors/defi/price_manipulation_high.py new file mode 100644 index 000000000..11cf2daec --- /dev/null +++ b/slither/detectors/defi/price_manipulation_high.py @@ -0,0 +1,255 @@ +from typing import List +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output +from slither.core.declarations import Contract +from slither.core.variables.state_variable import StateVariable +from slither.core.declarations import FunctionContract, Modifier +from slither.core.cfg.node import NodeType, Node +from slither.core.declarations.event import Event +from slither.core.expressions import CallExpression, Identifier +from slither.analyses.data_dependency.data_dependency import is_dependent + +from slither.core.declarations.solidity_variables import ( + SolidityFunction, +) +from slither.core.variables.local_variable import LocalVariable +from slither.core.variables.state_variable import StateVariable + +from slither.core.expressions import CallExpression +from slither.core.expressions.assignment_operation import AssignmentOperation + +from slither.slithir.operations import ( + EventCall, +) +from slither.detectors.defi.price_manipulation_tools import PriceManipulationTools + +class PriceManipulation(AbstractDetector): + """ + Detect when `msg.sender` is not used as `from` in transferFrom along with the use of permit. + """ + + ARGUMENT = "price-manipulation-high" + HELP = "transferFrom uses arbitrary from with permit" + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.MEDIUM + + WIKI = " https://metatrust.feishu.cn/wiki/wikcnley0RNMaoaSzdjcCpYxYoD" + + WIKI_TITLE = "The risk of price manipulation in DeFi projects" + WIKI_DESCRIPTION = ( + "Price manipulation is a common attack in DeFi projects. " + ) + WIKI_EXPLOIT_SCENARIO = """""" + + WIKI_RECOMMENDATION = """""" + # Functions that may return abnormal values due to price manipulation + ERC20_FUNCTION = [ + # "balanceOf", + # "totalSupply", + "getReserves", + # "balance", + "getAmountsOut", + "getAmountOut" +] + # TODO: Problems only occur when the affected variables are subject to addition or multiplication operations, otherwise there will be no problem + def checkIfHavePriceManipulation(self,contract:Contract): + result_dependent_data=[] + result_call_data=[] + if contract.is_interface: + return result_call_data,result_dependent_data + for function in contract.functions: + return_vars=[] + # Collection 1: Get all sensitive functions and variables related to fund transfer in the function DANGEROUS_ERC20_FUNCTION + dangerous_calls=self._get_all_dangerous_operation_variables(function) + # Collection 4: All assigned variables and the underlying ERC20 operations involved in the function + erc20_vars=[] + erc20_calls=[] + erc20_nodes=[] + for node in function.nodes: + # Get assigned variables and all involved underlying ERC20 operations in the node + node_vars,node_calls=self._get_calls_and_var_recursively_node(node) + if len(node_calls)>0: + erc20_vars.append(node_vars) + erc20_calls.append(node_calls) + erc20_nodes.append(node) + # Whether there is is_dependent between Collection 1 and Collection 4 variables + # All sensitive variables in the function + all_risk_vars = [arg.value for call in dangerous_calls for arg in call.arguments if isinstance(arg,Identifier) and (isinstance(arg.value,LocalVariable) or isinstance(arg.value,StateVariable))] + for risk_var in all_risk_vars: + for dangerous_erc20_vars,dangerous_erc20_calls,node in zip(erc20_vars,erc20_calls,erc20_nodes): + for dangerous_erc20_var,dangerous_erc20_call in zip(dangerous_erc20_vars,dangerous_erc20_calls): + if is_dependent(risk_var, dangerous_erc20_var, function): + result_dependent_data.append([function,risk_var,dangerous_erc20_var,dangerous_erc20_call,node]) + # print("risk variable in",function.name,":",risk_var.canonical_name,"rely on",dangerous_erc20_var.canonical_name,"with call:",dangerous_erc20_call) + return result_dependent_data,result_call_data + + # Recursive retrieval of child calls from functions + @staticmethod + def _get_calls_recursively(func: FunctionContract, maxdepth=10): + ret=[] + if maxdepth<=0: + return ret + if hasattr(func,"calls_as_expressions"): + if len(func.calls_as_expressions) > 0: + for call in func.calls_as_expressions: + if PriceManipulation._check_call_can_output(call): + if str(call.called.value) in PriceManipulation.ERC20_FUNCTION: + if not (len(call.arguments)==1 and str(call.arguments[0])=="address(this)"): + ret.append(call) + else: + ret.extend(PriceManipulation._get_calls_recursively(call.called.value,maxdepth=maxdepth-1)) + elif isinstance(call, CallExpression) and \ + call.called and not hasattr(call.called, 'value'): + # When there is an external call, only consider whether there is a balanceof and other ERC20 external calls, ignoring other calls + # Other calls can be added here to ensure that the data returned by external projects will not have problems or check potential problems + if hasattr(call.called,"member_name") and call.called.member_name in PriceManipulation.ERC20_FUNCTION: + if not (len(call.arguments)==1 and str(call.arguments[0])=="address(this)"): + ret.append(call) + return ret + + @staticmethod + def _check_if_can_output_call_info(call): + argument=call.arguments[0] + # balanceOf(a) + if (hasattr(argument,"value") and (isinstance(argument.value,StateVariable)) or "pair" in str(argument).lower()) or (hasattr(argument,"expression") and hasattr(argument.expression,"value") and (isinstance(argument.expression.value,StateVariable)) or "pair" in str(argument.expression.value).lower()): + return call + + # Get all sensitive operations related to transfer and minting in the function + @staticmethod + def _get_all_dangerous_operation_variables(func:FunctionContract): + ret_calls=[] + ret_vars=[] + for call in func.calls_as_expressions: + if (call.called and hasattr(call.called,"member_name") and call.called.member_name in PriceManipulationTools.DANGEROUS_ERC20_FUNCTION) or \ + (call.called and hasattr(call.called,"value") and call.called.value.name in PriceManipulationTools.DANGEROUS_ERC20_FUNCTION): + ret_calls.append(call) + return ret_calls + + # Get all return variables operations in the function + @staticmethod + def _get_all_return_variables(func:FunctionContract): + ret=[] + for node in func.nodes: + if node.will_return and len(node.variables_read)>0: + ret.extend(node.variables_read) + ret.extend(func.returns) + return ret + + # Get all sensitive function return operations in the function + @staticmethod + def _get_all_return_calls(func:FunctionContract): + ret_calls=[] + for node in func.nodes: + if node.will_return and "require" not in str(node) and hasattr(node,"calls_as_expression") and len(node.calls_as_expression)>0: + _,calls=PriceManipulation._get_calls_and_var_recursively_node(node) + + for call in calls: + if isinstance(call,SolidityFunction): + ret_calls.append((call,node)) + elif hasattr(call,"called") and \ + ((call.called and hasattr(call.called,"member_name") and call.called.member_name in PriceManipulation.ERC20_FUNCTION) or \ + (call.called and hasattr(call.called,"value") and call.called.value.name in PriceManipulation.ERC20_FUNCTION)): + ret_calls.append((call,node)) + return ret_calls + + # Get all assignment operations from the function + @staticmethod + def _get_all_assignment_for_variables(func:FunctionContract): + variable_assignment=[] + for node in func.nodes: + if isinstance(node.expression,AssignmentOperation): + variable_assignment=node.variables_written + if hasattr(node,"calls_as_expression") and len(node.calls_as_expression) > 0: + pass + + + + # Get all child calls related to erc20 balance and getreserve from the node + @staticmethod + def _get_calls_and_var_recursively_node(node: NodeType): + # Child calls + ret_calls=[] + # Variables related to balance + ret_vars=[] + variable_writtens=[] + if isinstance(node.expression,AssignmentOperation): + variable_writtens=node.variables_written # Save this variable if there is variable writing + # If it is used to calculate token difference before and after, do not consider this case, return directly + for var in variable_writtens: + if var is None: + continue + if "before" in str(var.name).lower() or "after" in str(var.name).lower(): + return [],[] + # If the node writes variables using call, output all calls involved in this node, including erc20 and others + if hasattr(node,"calls_as_expression") and len(node.calls_as_expression) > 0: + for call in node.calls_as_expression: + if PriceManipulation._check_call_can_output(call): + if call.called.value.full_name in PriceManipulation.ERC20_FUNCTION: + # Do not consider balanceOf(address(this)) + if not (len(call.arguments)==1 and str(call.arguments[0])=="address(this)"): + ret_calls.append(call) + else: + ret_calls.extend(PriceManipulation._get_calls_recursively(call.called.value)) + if call.called and hasattr(call.called,"member_name") and call.called.member_name in PriceManipulation.ERC20_FUNCTION: + # Do not consider balanceOf(address(this)) + if not (len(call.arguments)==1 and str(call.arguments[0])=="address(this)"): + ret_calls.append(call) + return variable_writtens,ret_calls + + @staticmethod + def _check_call_can_output(call): + return isinstance(call, CallExpression) and \ + call.called and hasattr(call.called, 'value') and \ + isinstance(call.called.value, FunctionContract) and \ + not isinstance(call.called.value,Modifier) and \ + not isinstance(call.called.value, Event) + + + def _check_contract_if_uniswap_fork(self,contract:Contract): + if set(PriceManipulationTools.UNISWAP_PAIR_FUNCTION).issubset(set(contract.functions_declared)) or set(PriceManipulationTools.UNISWAP_ROUTER_FUNCTION).issubset(set(contract.functions_declared)): + return True + return False + + + + + def _detect(self) -> List[Output]: + """""" + results: List[Output] = [] + result_dependent_data=[] + result_call_data=[] + info=[] + for c in self.contracts: + if c.name in PriceManipulationTools.SAFECONTRACTS: + continue + if c.is_interface: + continue + if self._check_contract_if_uniswap_fork(c): + continue + if any(router_name in c.name for router_name in ["Router","router"]): + continue + result_dependent_data,result_call_data=self.checkIfHavePriceManipulation(c) + exist_node=[] + if len(result_dependent_data)>0 or len(result_call_data)>0: + info = ["Potential price manipulation risk:\n"] + # print("risk variable in",function.name,":",risk_var.canonical_name,"rely on",dangerous_erc20_var.canonical_name,"with call:",dangerous_erc20_call) + # data[4] is the node that will actually have a problem, deduplicate according to data[4] + for data in result_dependent_data: + if data[4] not in exist_node and not any(isinstance(ir,EventCall) for ir in data[4].irs): + info += ["\t- In function ",str(data[0]),"\n", + "\t\t-- ",data[4]," have potential price manipulated risk from ",str(data[2])," and call ",str(data[3])," which could influence variable:",str(data[1]),"\n" + ] + exist_node.append(data[4]) + + # print("return call in",function.name,":",str(call[0]),"in return is dangerous") + # Deduplicate according to call[2] + for call in result_call_data: + if call[2] not in exist_node and not any(isinstance(ir,EventCall) for ir in call[2].irs): + info += ["\t- In function ",str(call[0]),"\n", + "\t\t-- ",call[2],"have potential price manipulated risk in return call ",str(call[1])," could influence return value\n" + ] + exist_node.append(call[2]) + res=self.generate_result(info) + results.append(res) + return results + diff --git a/slither/detectors/defi/price_manipulation_info.py b/slither/detectors/defi/price_manipulation_info.py new file mode 100644 index 000000000..1158e96d5 --- /dev/null +++ b/slither/detectors/defi/price_manipulation_info.py @@ -0,0 +1,209 @@ +from typing import List +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output +from slither.core.declarations import Contract +from slither.core.variables.state_variable import StateVariable +from slither.core.declarations import FunctionContract, Modifier +from slither.core.cfg.node import NodeType, Node +from slither.core.declarations.event import Event +from slither.core.expressions import CallExpression, Identifier +from slither.analyses.data_dependency.data_dependency import is_dependent + +from slither.core.declarations.solidity_variables import ( + SolidityFunction, +) +from slither.core.variables.local_variable import LocalVariable +from slither.core.variables.state_variable import StateVariable + +from slither.core.expressions import CallExpression +from slither.core.expressions.assignment_operation import AssignmentOperation + +from slither.slithir.operations import ( + EventCall, +) +from slither.detectors.defi.price_manipulation_tools import PriceManipulationTools + +class PriceManipulationInfo(AbstractDetector): + """ + Detect when `msg.sender` is not used as `from` in transferFrom along with the use of permit. + """ + + ARGUMENT = "price-manipulation-info" + HELP = "transferFrom uses arbitrary from with permit" + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.MEDIUM + + WIKI = " https://metatrust.feishu.cn/wiki/wikcnley0RNMaoaSzdjcCpYxYoD" + + WIKI_TITLE = "The risk of price manipulation in DeFi projects" + WIKI_DESCRIPTION = ( + "Price manipulation is a common attack in DeFi projects. " + ) + WIKI_EXPLOIT_SCENARIO = """""" + + WIKI_RECOMMENDATION = """""" + + ERC20_FUNCTION = [ + "balanceOf(address)", + "balance", + "balanceOf" +] + + def checkIfHavePriceManipulation(self, contract: Contract): + result_dependent_data = [] + result_call_data = [] + if contract.is_interface: + return result_call_data, result_dependent_data + for function in contract.functions: + return_vars = [] + return_vars = self._get_all_return_variables(function) + return_calls = self._get_all_return_calls(function) + erc20_vars = [] + erc20_calls = [] + erc20_nodes = [] + for node in function.nodes: + node_vars, node_calls = self._get_calls_and_var_recursively_node(node) + if len(node_vars) > 0: + erc20_vars.append(node_vars) + erc20_calls.append(node_calls) + erc20_nodes.append(node) + all_risk_vars = [] + if return_vars is not None: + all_risk_vars.extend(return_vars) + for risk_var in all_risk_vars: + for dangerous_erc20_vars, dangerous_erc20_calls, node in zip(erc20_vars, erc20_calls, erc20_nodes): + for dangerous_erc20_var, dangerous_erc20_call in zip(dangerous_erc20_vars, dangerous_erc20_calls): + if is_dependent(risk_var, dangerous_erc20_var, function): + result_dependent_data.append([function, risk_var, dangerous_erc20_var, dangerous_erc20_call, node]) + for call in return_calls: + result_call_data.append([function, call[0], call[1]]) + return result_call_data, result_dependent_data + + @staticmethod + def _get_all_return_variables(func: FunctionContract): + ret = [] + for node in func.nodes: + if node.will_return and len(node.variables_read) > 0: + ret.extend(node.variables_read) + ret.extend(func.returns) + return ret + + @staticmethod + def _get_all_return_calls(func: FunctionContract): + ret_calls = [] + for node in func.nodes: + if node.will_return and "require" not in str(node) and hasattr(node, "calls_as_expression") and len(node.calls_as_expression) > 0: + _, calls = PriceManipulationInfo._get_calls_and_var_recursively_node(node) + for call in calls: + if isinstance(call, SolidityFunction): + ret_calls.append((call, node)) + elif hasattr(call, "called") and ((call.called and hasattr(call.called, "member_name") and call.called.member_name in PriceManipulationInfo.ERC20_FUNCTION) or (call.called and hasattr(call.called, "value") and call.called.value.name in PriceManipulationInfo.ERC20_FUNCTION)): + ret_calls.append((call, node)) + return ret_calls + + @staticmethod + def _get_all_assignment_for_variables(func: FunctionContract): + variable_assignment = [] + for node in func.nodes: + if isinstance(node.expression, AssignmentOperation): + variable_assignment = node.variables_written + if hasattr(node, "calls_as_expression") and len(node.calls_as_expression) > 0: + pass + + @staticmethod + def _get_calls_and_var_recursively_node(node: NodeType): + ret_calls = [] + ret_vars = [] + variable_writtens = [] + if isinstance(node.expression, AssignmentOperation): + variable_writtens = node.variables_written + for var in variable_writtens: + if var is None: + continue + if "before" in str(var.name).lower() or "after" in str(var.name).lower(): + return [], [] + if hasattr(node, "calls_as_expression") and len(node.calls_as_expression) > 0: + for call in node.calls_as_expression: + if PriceManipulationInfo._check_call_can_output(call): + if call.called.value.full_name in PriceManipulationInfo.ERC20_FUNCTION: + if len(call.arguments) == 1 and not str(call.arguments[0]) in ["address(this)"]: + ret_calls.append(PriceManipulationInfo._check_if_can_output_call_info(call)) + else: + ret_calls.extend(PriceManipulationInfo._get_calls_recursively(call.called.value)) + if call.called and hasattr(call.called, "member_name") and call.called.member_name in PriceManipulationInfo.ERC20_FUNCTION: + if len(call.arguments) == 1 and not str(call.arguments[0]) in ["address(this)"]: + ret_calls.append(PriceManipulationInfo._check_if_can_output_call_info(call)) + if len(node.internal_calls) > 0 and "address(this)" not in str(node): + for call in node.internal_calls: + if call.name == "balance(address)": + ret_calls.append(call) + return variable_writtens, ret_calls + + @staticmethod + def _get_calls_recursively(func: FunctionContract, maxdepth=10): + ret = [] + if maxdepth <= 0: + return ret + if hasattr(func, "calls_as_expressions"): + if len(func.calls_as_expressions) > 0: + for call in func.calls_as_expressions: + if PriceManipulationInfo._check_call_can_output(call): + if str(call.called.value) in PriceManipulationInfo.ERC20_FUNCTION: + if len(call.arguments) == 1 and not str(call.arguments[0]) in ["address(this)"]: + ret.append(PriceManipulationInfo._check_if_can_output_call_info(call)) + else: + ret.extend(PriceManipulationInfo._get_calls_recursively(call.called.value, maxdepth=maxdepth-1)) + elif isinstance(call, CallExpression) and call.called and not hasattr(call.called, 'value'): + if hasattr(call.called, "member_name") and call.called.member_name in PriceManipulationInfo.ERC20_FUNCTION: + if len(call.arguments) == 1 and not str(call.arguments[0]) in ["address(this)"]: + ret.append(PriceManipulationInfo._check_if_can_output_call_info(call)) + return ret + + @staticmethod + def _check_call_can_output(call): + return isinstance(call, CallExpression) and call.called and hasattr(call.called, 'value') and isinstance(call.called.value, FunctionContract) and not isinstance(call.called.value, Modifier) and not isinstance(call.called.value, Event) + + @staticmethod + def _check_if_can_output_call_info(call): + argument = call.arguments[0] + if (hasattr(argument, "value") and (isinstance(argument.value, StateVariable)) or "pair" in str(argument).lower()) or (hasattr(argument, "expression") and hasattr(argument.expression, "value") and ((isinstance(argument.expression.value, StateVariable)) or "pair" in str(argument.expression.value).lower())): + return call + + def _check_contract_if_uniswap_fork(self, contract: Contract): + if set(PriceManipulationTools.UNISWAP_PAIR_FUNCTION).issubset(set(contract.functions_declared)) or set(PriceManipulationTools.UNISWAP_ROUTER_FUNCTION).issubset(set(contract.functions_declared)): + return True + return False + + def _detect(self) -> List[Output]: + results: List[Output] = [] + result_dependent_data = [] + result_call_data = [] + info = [] + for c in self.contracts: + if c.name in PriceManipulationTools.SAFECONTRACTS: + continue + if c.is_interface: + continue + if self._check_contract_if_uniswap_fork(c): + continue + if any(router_name in c.name for router_name in ["Router","router"]): + continue + result_call_data, result_dependent_data = self.checkIfHavePriceManipulation(c) + exist_node = [] + if len(result_dependent_data) > 0 or len(result_call_data) > 0: + info = ["Potential price manipulation risk:\n"] + for data in result_dependent_data: + if data[4] not in exist_node and not any(isinstance(ir, EventCall) for ir in data[4].irs): + info += ["\t- In function ", str(data[0]), "\n", + "\t\t-- ", data[4], " have potential price manipulated risk from ", str(data[2]), " and call ", str(data[3]), " which could influence variable:", str(data[1]), "\n" + ] + exist_node.append(data[4]) + for call in result_call_data: + if call[2] not in exist_node and not any(isinstance(ir, EventCall) for ir in call[2].irs): + info += ["\t- In function ", str(call[0]), "\n", + "\t\t-- ", call[2], "have potential price manipulated risk in return call ", str(call[1]), " could influence return value\n" + ] + exist_node.append(call[2]) + res = self.generate_result(info) + results.append(res) + return results diff --git a/slither/detectors/defi/price_manipulation_low.py b/slither/detectors/defi/price_manipulation_low.py new file mode 100644 index 000000000..0d71a161b --- /dev/null +++ b/slither/detectors/defi/price_manipulation_low.py @@ -0,0 +1,257 @@ +from typing import List +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output +from slither.core.declarations import Contract +from slither.core.variables.state_variable import StateVariable +from slither.core.declarations import FunctionContract, Modifier +from slither.core.cfg.node import NodeType, Node +from slither.core.declarations.event import Event +from slither.core.expressions import CallExpression, Identifier +from slither.analyses.data_dependency.data_dependency import is_dependent + +from slither.core.declarations.solidity_variables import ( + SolidityFunction, +) +from slither.core.variables.local_variable import LocalVariable +from slither.core.variables.state_variable import StateVariable + +from slither.core.expressions import CallExpression +from slither.core.expressions.assignment_operation import AssignmentOperation + +from slither.slithir.operations import ( + EventCall, +) +from slither.detectors.defi.price_manipulation_tools import PriceManipulationTools + + +class PriceManipulationLow(AbstractDetector): + """ + Detect when `msg.sender` is not used as `from` in transferFrom along with the use of permit. + """ + + ARGUMENT = "price-manipulation-low" + HELP = "transferFrom uses arbitrary from with permit" + IMPACT = DetectorClassification.LOW + CONFIDENCE = DetectorClassification.MEDIUM + + WIKI = " https://metatrust.feishu.cn/wiki/wikcnley0RNMaoaSzdjcCpYxYoD" + + WIKI_TITLE = "The risk of price manipulation in DeFi projects" + WIKI_DESCRIPTION = ( + "Price manipulation is a common attack in DeFi projects. " + ) + WIKI_EXPLOIT_SCENARIO = """""" + + WIKI_RECOMMENDATION = """""" + # Functions that may return abnormal values due to price manipulation + ERC20_FUNCTION = [ + # "balanceOf", + # "totalSupply", + "getReserves", + # "balance", + "getAmountsOut", + "getAmountOut" +] + def checkIfHavePriceManipulation(self,contract:Contract): + result_dependent_data=[] + result_call_data=[] + if contract.is_interface: + return result_call_data,result_dependent_data + for function in contract.functions: + return_vars=[] + # Collection 2: Get all variables involved in function returns + return_vars=self._get_all_return_variables(function) + # Collection 3: Get all ERC20 operations involved in function returns + return_calls=self._get_all_return_calls(function) + # Collection 4: Variables assigned in the function along with associated ERC20 operations + erc20_vars=[] + erc20_calls=[] + erc20_nodes=[] + for node in function.nodes: + # Get variables assigned in the node and all associated ERC20 operations + node_vars,node_calls=self._get_calls_and_var_recursively_node(node) + if len(node_calls)>0: + erc20_vars.append(node_vars) + erc20_calls.append(node_calls) + erc20_nodes.append(node) + # Check if variables in Collection 2 and Collection 4 are dependent + # All sensitive variables in the function + all_risk_vars=[] + if return_vars is not None: + all_risk_vars.extend(return_vars) + for risk_var in all_risk_vars: + for dangerous_erc20_vars,dangerous_erc20_calls,node in zip(erc20_vars,erc20_calls,erc20_nodes): + for dangerous_erc20_var,dangerous_erc20_call in zip(dangerous_erc20_vars,dangerous_erc20_calls): + if is_dependent(risk_var, dangerous_erc20_var, function): + result_dependent_data.append([function,risk_var,dangerous_erc20_var,dangerous_erc20_call,node]) + # Output Collection 3 + for call in return_calls: + result_call_data.append([function,call[0],call[1]]) + return result_dependent_data,result_call_data + + # Recursively get the child calls of a function + @staticmethod + def _get_calls_recursively(func: FunctionContract, maxdepth=10): + ret=[] + if maxdepth<=0: + return ret + if hasattr(func,"calls_as_expressions"): + if len(func.calls_as_expressions) > 0: + for call in func.calls_as_expressions: + if PriceManipulationLow._check_call_can_output(call): + if str(call.called.value) in PriceManipulationLow.ERC20_FUNCTION: + if not (len(call.arguments)==1 and str(call.arguments[0])=="address(this)"): + ret.append(call) + else: + ret.extend(PriceManipulationLow._get_calls_recursively(call.called.value,maxdepth=maxdepth-1)) + elif isinstance(call, CallExpression) and \ + call.called and not hasattr(call.called, 'value'): + # When there is an external call, only consider ERC20 balanceof and similar calls + if hasattr(call.called,"member_name") and call.called.member_name in PriceManipulationLow.ERC20_FUNCTION: + if not (len(call.arguments)==1 and str(call.arguments[0])=="address(this)"): + ret.append(call) + return ret + + @staticmethod + def _check_if_can_output_call_info(call): + argument=call.arguments[0] + # balanceOf(a) + if (hasattr(argument,"value") and (isinstance(argument.value,StateVariable)) or "pair" in str(argument).lower()) or (hasattr(argument,"expression") and hasattr(argument.expression,"value") and (isinstance(argument.expression.value,StateVariable)) or "pair" in str(argument.expression.value).lower()): + return call + + # Get all sensitive operations related to transfer and minting in a function + @staticmethod + def _get_all_dangerous_operation_variables(func:FunctionContract): + ret_calls=[] + ret_vars=[] + for call in func.calls_as_expressions: + if (call.called and hasattr(call.called,"member_name") and call.called.member_name in PriceManipulationTools.DANGEROUS_ERC20_FUNCTION) or \ + (call.called and hasattr(call.called,"value") and call.called.value.name in PriceManipulationTools.DANGEROUS_ERC20_FUNCTION): + ret_calls.append(call) + return ret_calls + + # Get all variables returned in a function + @staticmethod + def _get_all_return_variables(func:FunctionContract): + ret=[] + for node in func.nodes: + if node.will_return and len(node.variables_read)>0: + ret.extend(node.variables_read) + ret.extend(func.returns) + return ret + + # Get all sensitive function calls returned in a function + @staticmethod + def _get_all_return_calls(func:FunctionContract): + ret_calls=[] + for node in func.nodes: + if node.will_return and "require" not in str(node) and hasattr(node,"calls_as_expression") and len(node.calls_as_expression)>0: + _,calls=PriceManipulationLow._get_calls_and_var_recursively_node(node) + + for call in calls: + if isinstance(call,SolidityFunction): + ret_calls.append((call,node)) + elif hasattr(call,"called") and \ + ((call.called and hasattr(call.called,"member_name") and call.called.member_name in PriceManipulationLow.ERC20_FUNCTION) or \ + (call.called and hasattr(call.called,"value") and call.called.value.name in PriceManipulationLow.ERC20_FUNCTION)): + ret_calls.append((call,node)) + return ret_calls + + # Get all assignment operations from a function + @staticmethod + def _get_all_assignment_for_variables(func:FunctionContract): + variable_assignment=[] + for node in func.nodes: + if isinstance(node.expression,AssignmentOperation): + variable_assignment=node.variables_written + if hasattr(node,"calls_as_expression") and len(node.calls_as_expression) > 0: + pass + + + + # Get all child calls related to ERC20 balance and getreserve from a node + @staticmethod + def _get_calls_and_var_recursively_node(node: NodeType): + # Child calls + ret_calls=[] + # Variables related to balance + ret_vars=[] + variable_writtens=[] + if isinstance(node.expression,AssignmentOperation): + variable_writtens=node.variables_written # Save the written variable if exists + # If it's for calculating token difference before and after, directly return + for var in variable_writtens: + if var is None: + continue + if "before" in str(var.name).lower() or "after" in str(var.name).lower(): + return [],[] + # If the node uses call for variable writing, output all associated calls of this node, including ERC20 and others + if hasattr(node,"calls_as_expression") and len(node.calls_as_expression) > 0: + for call in node.calls_as_expression: + if PriceManipulationLow._check_call_can_output(call): + if call.called.value.full_name in PriceManipulationLow.ERC20_FUNCTION: + # Do not consider balanceOf(address(this)) + if not (len(call.arguments)==1 and str(call.arguments[0])=="address(this)"): + ret_calls.append(call) + else: + ret_calls.extend(PriceManipulationLow._get_calls_recursively(call.called.value)) + if call.called and hasattr(call.called,"member_name") and call.called.member_name in PriceManipulationLow.ERC20_FUNCTION: + # Do not consider balanceOf(address(this)) + if not (len(call.arguments)==1 and str(call.arguments[0])=="address(this)"): + ret_calls.append(call) + return variable_writtens,ret_calls + + @staticmethod + def _check_call_can_output(call): + return isinstance(call, CallExpression) and \ + call.called and hasattr(call.called, 'value') and \ + isinstance(call.called.value, FunctionContract) and \ + not isinstance(call.called.value,Modifier) and \ + not isinstance(call.called.value, Event) + + + def _check_contract_if_uniswap_fork(self,contract:Contract): + if set(PriceManipulationTools.UNISWAP_PAIR_FUNCTION).issubset(set(contract.functions_declared)) or set(PriceManipulationTools.UNISWAP_ROUTER_FUNCTION).issubset(set(contract.functions_declared)): + return True + return False + + + + + def _detect(self) -> List[Output]: + """""" + results: List[Output] = [] + result_dependent_data=[] + result_call_data=[] + info=[] + for c in self.contracts: + if c.name in PriceManipulationTools.SAFECONTRACTS: + continue + if c.is_interface: + continue + if self._check_contract_if_uniswap_fork(c): + continue + if any(router_name in c.name for router_name in ["Router","router"]): + continue + result_dependent_data,result_call_data=self.checkIfHavePriceManipulation(c) + exist_node=[] + if len(result_dependent_data)>0 or len(result_call_data)>0: + info = ["Potential price manipulation risk:\n"] + # data[4] is the actual node that may have issues, deduplicate based on data[4] + for data in result_dependent_data: + if data[4] not in exist_node and not any(isinstance(ir,EventCall) for ir in data[4].irs): + info += ["\t- In function ",str(data[0]),"\n", + "\t\t-- ",data[4]," have potential price manipulated risk from ",str(data[2])," and call ",str(data[3])," which could influence variable:",str(data[1]),"\n" + ] + exist_node.append(data[4]) + + # Deduplicate based on call[2] + for call in result_call_data: + if call[2] not in exist_node and not any(isinstance(ir,EventCall) for ir in call[2].irs): + info += ["\t- In function ",str(call[0]),"\n", + "\t\t-- ",call[2],"have potential price manipulated risk in return call ",str(call[1])," could influence return value\n" + ] + exist_node.append(call[2]) + res=self.generate_result(info) + results.append(res) + return results diff --git a/slither/detectors/defi/price_manipulation_medium.py b/slither/detectors/defi/price_manipulation_medium.py new file mode 100644 index 000000000..b7940a3bc --- /dev/null +++ b/slither/detectors/defi/price_manipulation_medium.py @@ -0,0 +1,260 @@ +from typing import List +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output +from slither.core.declarations import Contract +from slither.core.variables.state_variable import StateVariable +from slither.core.declarations import FunctionContract, Modifier +from slither.core.cfg.node import NodeType, Node +from slither.core.declarations.event import Event +from slither.core.expressions import CallExpression, Identifier +from slither.analyses.data_dependency.data_dependency import is_dependent + +from slither.core.declarations.solidity_variables import ( + SolidityFunction, +) +from slither.core.variables.local_variable import LocalVariable +from slither.core.variables.state_variable import StateVariable + +from slither.core.expressions import CallExpression +from slither.core.expressions.assignment_operation import AssignmentOperation + +from slither.slithir.operations import ( + EventCall, +) +from slither.detectors.defi.price_manipulation_tools import PriceManipulationTools + + +class PriceManipulationMedium(AbstractDetector): + """ + Detect when `msg.sender` is not used as `from` in transferFrom along with the use of permit. + """ + + ARGUMENT = "price-manipulation-medium" + HELP = "transferFrom uses arbitrary from with permit" + IMPACT = DetectorClassification.MEDIUM + CONFIDENCE = DetectorClassification.MEDIUM + + WIKI = " https://metatrust.feishu.cn/wiki/wikcnley0RNMaoaSzdjcCpYxYoD" + + WIKI_TITLE = "The risk of price manipulation in DeFi projects" + WIKI_DESCRIPTION = ( + "Price manipulation is a common attack in DeFi projects. " + ) + WIKI_EXPLOIT_SCENARIO = """""" + + WIKI_RECOMMENDATION = """""" + # Functions that may return abnormal values due to price manipulation + ERC20_FUNCTION = [ + "balanceOf(address)", + "balanceOf", + # "totalSupply", + # "getReserves", + "balance", + # "getAmountsOut", + # "getAmountOut" +] + def checkIfHavePriceManipulation(self,contract:Contract): + result_dependent_data=[] + result_call_data=[] + if contract.is_interface: + return result_call_data,result_dependent_data + for function in contract.functions: + return_vars=[] + # Set 1: Get all sensitive functions related to fund transfer and their associated variables DANGEROUS_ERC20_FUNCTION + dangerous_calls=self._get_all_dangerous_operation_variables(function) + # Set 4: All assigned variables and their associated underlying ERC20 operations in the function + erc20_vars=[] + erc20_calls=[] + erc20_nodes=[] + for node in function.nodes: + # Get variables and all associated underlying ERC20 operations from the node + node_vars,node_calls=self._get_calls_and_var_recursively_node(node) + if len(node_vars)>0: + erc20_vars.append(node_vars) + erc20_calls.append(node_calls) + erc20_nodes.append(node) + # Check if there are dependencies between variables in Set 1 and Set 4 + all_risk_vars = [arg.value for call in dangerous_calls for arg in call.arguments if isinstance(arg,Identifier) and (isinstance(arg.value,LocalVariable) or isinstance(arg.value,StateVariable))] + for risk_var in all_risk_vars: + for dangerous_erc20_vars,dangerous_erc20_calls,node in zip(erc20_vars,erc20_calls,erc20_nodes): + for dangerous_erc20_var,dangerous_erc20_call in zip(dangerous_erc20_vars,dangerous_erc20_calls): + if is_dependent(risk_var, dangerous_erc20_var, function): + result_dependent_data.append([function,risk_var,dangerous_erc20_var,dangerous_erc20_call,node]) + # print("risk variable in",function.name,":",risk_var.canonical_name,"rely on",dangerous_erc20_var.canonical_name,"with call:",dangerous_erc20_call) + return result_dependent_data,result_call_data + + # Get all sensitive operations related to transfer and mint in the function + @staticmethod + def _get_all_dangerous_operation_variables(func:FunctionContract): + ret_calls=[] + ret_vars=[] + for call in func.calls_as_expressions: + if (call.called and hasattr(call.called,"member_name") and call.called.member_name in PriceManipulationTools.DANGEROUS_ERC20_FUNCTION) or \ + (call.called and hasattr(call.called,"value") and call.called.value.name in PriceManipulationTools.DANGEROUS_ERC20_FUNCTION): + ret_calls.append(call) + return ret_calls + + # Get all return variables in the function + @staticmethod + def _get_all_return_variables(func:FunctionContract): + ret=[] + for node in func.nodes: + if node.will_return and len(node.variables_read)>0: + ret.extend(node.variables_read) + ret.extend(func.returns) + return ret + + # Get all sensitive function calls in the return statements + @staticmethod + def _get_all_return_calls(func:FunctionContract): + ret_calls=[] + for node in func.nodes: + if node.will_return and "require" not in str(node) and hasattr(node,"calls_as_expression") and len(node.calls_as_expression)>0: + _,calls=PriceManipulationMedium._get_calls_and_var_recursively_node(node) + + for call in calls: + if isinstance(call,SolidityFunction): + ret_calls.append((call,node)) + elif hasattr(call,"called") and \ + ((call.called and hasattr(call.called,"member_name") and call.called.member_name in PriceManipulationMedium.ERC20_FUNCTION) or \ + (call.called and hasattr(call.called,"value") and call.called.value.name in PriceManipulationMedium.ERC20_FUNCTION)): + ret_calls.append((call,node)) + return ret_calls + + # Get all assignment operations from the function + @staticmethod + def _get_all_assignment_for_variables(func:FunctionContract): + variable_assignment=[] + for node in func.nodes: + if isinstance(node.expression,AssignmentOperation): + variable_assignment=node.variables_written + if hasattr(node,"calls_as_expression") and len(node.calls_as_expression) > 0: + pass + + + + # Get all underlying erc20 balance and getreserve sub-calls from the node + @staticmethod + def _get_calls_and_var_recursively_node(node: NodeType): + # Sub-calls + ret_calls=[] + # Variables related to balance + ret_vars=[] + variable_writtens=[] + if isinstance(node.expression,AssignmentOperation): + variable_writtens=node.variables_written # If there's variable written, save it + # If it's used for calculating token difference between before and after, return without considering + for var in variable_writtens: + if var is None: + continue + if "before" in str(var.name).lower() or "after" in str(var.name).lower(): + return [],[] + # If the node uses call for variable writing, output all associated calls of this node, including erc20 and others + if hasattr(node,"calls_as_expression") and len(node.calls_as_expression) > 0: + for call in node.calls_as_expression: + if PriceManipulationMedium._check_call_can_output(call): + if call.called.value.full_name in PriceManipulationMedium.ERC20_FUNCTION: + # Do not consider balanceOf(address(this)) + if len(call.arguments)==1 and not str(call.arguments[0]) in ["address(this)"]: + ret_calls.append(PriceManipulationMedium._check_if_can_output_call_info(call)) + else: + ret_calls.extend(PriceManipulationMedium._get_calls_recursively(call.called.value)) + if call.called and hasattr(call.called,"member_name") and call.called.member_name in PriceManipulationMedium.ERC20_FUNCTION: + # Do not consider balanceOf(address(this)) + if len(call.arguments)==1 and not str(call.arguments[0]) in ["address(this)"]: + ret_calls.append(PriceManipulationMedium._check_if_can_output_call_info(call)) + # Includes all address.balance + # Excludes address(this) + if len(node.internal_calls) > 0 and "address(this)" not in str(node): + for call in node.internal_calls: + if call.name=="balance(address)": + ret_calls.append(call) + return variable_writtens,ret_calls + + # Recursive function to get sub-calls of the function + @staticmethod + def _get_calls_recursively(func: FunctionContract, maxdepth=10): + ret=[] + if maxdepth<=0: + return ret + if hasattr(func,"calls_as_expressions"): + if len(func.calls_as_expressions) > 0: + for call in func.calls_as_expressions: + if PriceManipulationMedium._check_call_can_output(call): + if str(call.called.value) in PriceManipulationMedium.ERC20_FUNCTION: + if len(call.arguments)==1 and not str(call.arguments[0]) in ["address(this)","msg.sender"]: + ret.append(PriceManipulationMedium._check_if_can_output_call_info(call)) + else: + ret.extend(PriceManipulationMedium._get_calls_recursively(call.called.value,maxdepth=maxdepth-1)) + elif isinstance(call, CallExpression) and \ + call.called and not hasattr(call.called, 'value'): + # When there's external call, only consider ERC20's external call, ignore others + # This could be extended to include other calls to ensure no issues with external project returns or check potential issues + if hasattr(call.called,"member_name") and call.called.member_name in PriceManipulationMedium.ERC20_FUNCTION: + if len(call.arguments)==1 and not str(call.arguments[0]) in ["address(this)","msg.sender"]: + ret.append(PriceManipulationMedium._check_if_can_output_call_info(call)) + return ret + + + @staticmethod + def _check_call_can_output(call): + return isinstance(call, CallExpression) and \ + call.called and hasattr(call.called, 'value') and \ + isinstance(call.called.value, FunctionContract) and \ + not isinstance(call.called.value,Modifier) and \ + not isinstance(call.called.value, Event) + + @staticmethod + def _check_if_can_output_call_info(call): + argument=call.arguments[0] + # balanceOf(a) + if (hasattr(argument,"value") and (isinstance(argument.value,StateVariable)) or \ + "pair" in str(argument).lower()) or \ + (hasattr(argument,"expression") and hasattr(argument.expression,"value") and \ + ((isinstance(argument.expression.value,StateVariable)) or "pair" in str(argument.expression.value).lower())): + return call + + def _check_contract_if_uniswap_fork(self,contract:Contract): + if set(PriceManipulationTools.UNISWAP_PAIR_FUNCTION).issubset(set(contract.functions_declared)) or set(PriceManipulationTools.UNISWAP_ROUTER_FUNCTION).issubset(set(contract.functions_declared)): + return True + return False + + def _detect(self) -> List[Output]: + """""" + results: List[Output] = [] + result_dependent_data=[] + result_call_data=[] + info=[] + for c in self.contracts: + if c.name in PriceManipulationTools.SAFECONTRACTS: + continue + if c.is_interface: + continue + if self._check_contract_if_uniswap_fork(c): + continue + if any(router_name in c.name for router_name in ["Router","router"]): + continue + result_dependent_data,result_call_data=self.checkIfHavePriceManipulation(c) + exist_node=[] + if len(result_dependent_data)>0 or len(result_call_data)>0: + info = ["Potential price manipulation risk:\n"] + # print("risk variable in",function.name,":",risk_var.canonical_name,"rely on",dangerous_erc20_var.canonical_name,"with call:",dangerous_erc20_call) + # data[4] is the actual problematic node, remove duplicates based on data[4] + for data in result_dependent_data: + if data[4] not in exist_node and not any(isinstance(ir,EventCall) for ir in data[4].irs): + info += ["\t- In function ",str(data[0]),"\n", + "\t\t-- ",data[4]," have potential price manipulated risk from ",str(data[2])," and call ",str(data[3])," which could influence variable:",str(data[1]),"\n" + ] + exist_node.append(data[4]) + + # print("return call in",function.name,":",str(call[0]),"in return is dangerous") + # Remove duplicates based on call[2] + for call in result_call_data: + if call[2] not in exist_node and not any(isinstance(ir,EventCall) for ir in call[2].irs): + info += ["\t- In function ",str(call[0]),"\n", + "\t\t-- ",call[2],"have potential price manipulated risk in return call ",str(call[1])," could influence return value\n" + ] + exist_node.append(call[2]) + res=self.generate_result(info) + results.append(res) + return results diff --git a/slither/detectors/defi/price_manipulation_tools.py b/slither/detectors/defi/price_manipulation_tools.py new file mode 100644 index 000000000..4f24ce54e --- /dev/null +++ b/slither/detectors/defi/price_manipulation_tools.py @@ -0,0 +1,75 @@ +from typing import List +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output +from slither.core.declarations import Contract +from slither.core.variables.state_variable import StateVariable +from slither.core.declarations import FunctionContract, Modifier +from slither.core.cfg.node import NodeType, Node +from slither.core.declarations.event import Event +from slither.core.expressions import CallExpression, Identifier +from slither.analyses.data_dependency.data_dependency import is_dependent + +from slither.core.declarations.solidity_variables import ( + SolidityFunction, +) +from slither.core.variables.local_variable import LocalVariable +from slither.core.variables.state_variable import StateVariable + +from slither.core.expressions import CallExpression +from slither.core.expressions.assignment_operation import AssignmentOperation + +from slither.slithir.operations import ( + EventCall, +) +from slither.detectors.defi.price_manipulation_tools import PriceManipulationTools + + +class PriceManipulationTools: + # 涉及到资金操作(如转账)的敏感函数,这些函数可能会因为价格操控导致其中参数出现异常从而导致异常资金操作 + DANGEROUS_ERC20_FUNCTION = [ + "transferFrom", + "safeTransferFrom", + "mint", + "burn", + "burnFrom", + "transfer", + "send" + "safeTransfer", + "getReward", + "_transferFrom", + "_safeTransferFrom", + "_mint", + "_burn", + "_burnFrom", + "_transfer", + "_safeTransfer", + "_getReward", + "_internalTransfer" + ] + UNISWAP_ROUTER_FUNCTION=[ + "_addLiquidity", + "addLiquidity", + "removeLiquidity", + "swapTokensForExactTokens", + "swapExactTokensForTokens" + ] + UNISWAP_PAIR_FUNCTION=[ + "_update", + "burn", + "mint", + "swap", + "skim" + ] + COMMON_FUNCTION = [ + "deposit","withdraw","lending","redeem","borrow","liquidate","claim","getReward" + ] + # 喂价函数,仅作为返回值异常检测用 + PRICE_FEED=[ + "eps3ToWant","latestAnswer", + "extrapolatePoolValueFromToken","getPrice", + "unsafeValueOfAsset","valueOfAsset" + ] + SAFECONTRACTS=["UniswapV2Library","UniswapV2OracleLibrary","UniswapV2Pair","UniswapV2Router02","UniswapV2Factory", + "SushiswapV2Factory","SushiswapV2Router02","SushiswapV2Pair","SushiswapV2Library", + "SushiSwapProxy","Pair","PancakeLibrary","PancakePair","PancakeRouter","PancakeFactory"] + \ No newline at end of file diff --git a/slither/detectors/functions/centralized_info.py b/slither/detectors/functions/centralized_info.py new file mode 100644 index 000000000..82ca0e14f --- /dev/null +++ b/slither/detectors/functions/centralized_info.py @@ -0,0 +1,79 @@ +""" +Module detecting modifiers that are not guaranteed to execute _; or revert()/throw + +Note that require()/assert() are not considered here. Even if they +are in the outermost scope, they do not guarantee a revert, so a +default value can still be returned. +""" +import json +from slither.core.declarations.solidity_variables import SolidityVariableComposed +from slither.core.expressions import CallExpression +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.cfg.node import NodeType +from slither.core.declarations import ( + Contract, + Pragma, + Import, + Function, + Modifier, +) +from slither.core.declarations.event import Event +from slither.core.declarations import FunctionContract, Modifier +from slither.core.declarations import ( + SolidityFunction, +) +from slither.core.solidity_types.elementary_type import ElementaryType +from slither.detectors.functions.centralized_utils import CentralizedUtil + +from slither.slithir.operations import SolidityCall,InternalCall +from slither.slithir.operations.binary import Binary +from slither.slithir.operations.index import Index +from slither.slithir.operations.binary import BinaryType +from slither.detectors.functions.modifier_utils import ModifierUtil + + + + +class CentralizedRiskInfo(AbstractDetector): + """ + Detector for centralized risk in smart contracts. + """ + + ARGUMENT = "centralized-risk-informational" + HELP = "Detects modifiers that may introduce centralized risk." + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH + WIKI = " " + + WIKI_TITLE = "Centralized Risk With function change state" + WIKI_DESCRIPTION = "This detector identifies potential instances of centralized risk in smart contracts." + + # region wiki_exploit_scenario + WIKI_EXPLOIT_SCENARIO = """Consider a scenario where a smart contract has a function that allows a user to change the state of the contract. If the function is not properly secured, an attacker could exploit the contract by changing the state of the contract in a way that benefits them. This could lead to a loss of funds or other negative outcomes for the contract owner and users. By identifying potential instances of centralized risk in smart contracts, developers can take steps to secure their contracts and protect against potential exploits.""" + # endregion wiki_exploit_scenario + + WIKI_RECOMMENDATION = "" + def _detect(self): + ''' + This function is used to detect the centralized risk in the contract + ''' + + results = [] + contract_info=[] + for c in self.contracts: + # contract_info = ["centralized risk found in ", c, '\n'] + + for function in c.functions: + if function.name.lower() in ["transfer","transferfrom"]: + continue + if CentralizedUtil.check_if_function_change_key_state(function): + if function.visibility in ["public", "external"] and not function.view: + centralized_info_functions = CentralizedUtil.detect_function_if_centralized(function) + for centralized_info_function in centralized_info_functions: + if centralized_info_function['oz_read_or_written'] or \ + centralized_info_function['function_modifier_info']: + function_info = CentralizedUtil.output_function_centralized_info(function) + contract_info.append(self.generate_result(["\t- ", function, "\n"])) + results.extend(contract_info) if contract_info else None + return results + diff --git a/slither/detectors/functions/centralized_init_supply.py b/slither/detectors/functions/centralized_init_supply.py new file mode 100644 index 000000000..d24318d17 --- /dev/null +++ b/slither/detectors/functions/centralized_init_supply.py @@ -0,0 +1,88 @@ +""" +Module detecting modifiers that are not guaranteed to execute _; or revert()/throw + +Note that require()/assert() are not considered here. Even if they +are in the outermost scope, they do not guarantee a revert, so a +default value can still be returned. +""" +import json +from slither.core.declarations.solidity_variables import SolidityVariableComposed +from slither.core.expressions import CallExpression +from slither.core.variables.state_variable import StateVariable +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.cfg.node import NodeType +from slither.core.declarations import ( + Contract, + Pragma, + Import, + Function, + Modifier, +) +from slither.core.declarations.event import Event +from slither.core.declarations import FunctionContract, Modifier +from slither.core.declarations import ( + SolidityFunction, +) +from slither.core.solidity_types.elementary_type import ElementaryType +from slither.detectors.functions.centralized_utils import CentralizedUtil + +from slither.slithir.operations import SolidityCall,InternalCall +from slither.slithir.operations.binary import Binary +from slither.slithir.operations.index import Index +from slither.slithir.operations.binary import BinaryType +from slither.detectors.functions.modifier_utils import ModifierUtil + + + + +class CentralizedInitSupply(AbstractDetector): + """ + Detector for modifiers that return a default value + """ + + ARGUMENT = "centralized-init-supply" + HELP = "centrlized risk with supply totalsupply token in constructor during initial the contract" + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH + WIKI = " " + + WIKI_TITLE = "Centralized Risk with supply" + WIKI_DESCRIPTION = "centrlized risk with supply totalsupply token in constructor during initial the contract" + + # region wiki_exploit_scenario + WIKI_EXPLOIT_SCENARIO = """ + centrlized risk with supply totalsupply token in constructor during initial the contract """ + # endregion wiki_exploit_scenario + + WIKI_RECOMMENDATION = "centrlized risk with supply totalsupply token in constructor during initial the contract" + + def _detect(self): + + results = [] + for c in self.contracts: + if c.constructor: + for node in c.constructor.nodes: + if "mint" in str(node).lower(): + contract_info = ["centralized risk found in ", node, 'which has a token supply distribution in constructor \n'] + res = self.generate_result(contract_info) + results.append(res) + if len(node.variables_written)>0: + if any(isinstance(var_written,StateVariable) and var_written.full_name.lower() in ["balances(address)","_balances(address)","_rowned(address)"] for var_written in node.variables_written): + contract_info = ["centralized risk found in ", node, 'which has a token supply distribution in constructor \n'] + res = self.generate_result(contract_info) + results.append(res) + for f in c.functions: + if "init" in f.name.lower(): + for node in f.nodes: + if "mint" in str(node).lower(): + contract_info = ["centralized risk found in ", node, 'which has a token supply distribution in constructor \n'] + res = self.generate_result(contract_info) + results.append(res) + if len(node.variables_written)>0: + if any(isinstance(var_written,StateVariable) and var_written.full_name.lower() in ["balances(address)","_balances(address)","_rowned(address)"] for var_written in node.variables_written): + contract_info = ["centralized risk found in ", node, 'which has a token supply distribution in constructor \n'] + res = self.generate_result(contract_info) + results.append(res) + + return results + diff --git a/slither/detectors/functions/centralized_low.py b/slither/detectors/functions/centralized_low.py new file mode 100644 index 000000000..0387a1de4 --- /dev/null +++ b/slither/detectors/functions/centralized_low.py @@ -0,0 +1,79 @@ +""" +Module detecting modifiers that are not guaranteed to execute _; or revert()/throw + +Note that require()/assert() are not considered here. Even if they +are in the outermost scope, they do not guarantee a revert, so a +default value can still be returned. +""" +import json +import json +from slither.core.declarations.solidity_variables import SolidityVariableComposed +from slither.core.expressions import CallExpression +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.cfg.node import NodeType +from slither.core.declarations import ( + Contract, + Pragma, + Import, + Function, + Modifier, +) +from slither.core.declarations.event import Event +from slither.core.declarations import FunctionContract, Modifier +from slither.core.declarations import ( + SolidityFunction, +) +from slither.core.solidity_types.elementary_type import ElementaryType +from slither.detectors.functions.centralized_utils import CentralizedUtil + +from slither.slithir.operations import SolidityCall,InternalCall +from slither.slithir.operations.binary import Binary +from slither.slithir.operations.index import Index +from slither.slithir.operations.binary import BinaryType +from slither.detectors.functions.modifier_utils import ModifierUtil + + + +class CentralizedRiskLOW(AbstractDetector): + """ + Detector for centralized risk in smart contracts with low impact and high confidence. + """ + + ARGUMENT = "centralized-risk-low" + HELP = "Detects modifiers that may introduce centralized risk by returning a default value." + IMPACT = DetectorClassification.LOW + CONFIDENCE = DetectorClassification.HIGH + WIKI = " " + + WIKI_TITLE = "Centralized Risk with function read key state" + WIKI_DESCRIPTION = "The Centralized Risk detector identifies patterns in smart contracts that introduce centralized risk, potentially affecting the decentralization and security of the system." + + # region wiki_exploit_scenario + WIKI_EXPLOIT_SCENARIO = """""" + # endregion wiki_exploit_scenario + + WIKI_RECOMMENDATION = "" + + def _detect(self): + ''' + This function is used to detect the centralized risk in the contract + ''' + results = [] + contract_info=[] + + for c in self.contracts: + # contract_info = ["centralized risk found in", c, '\n'] + for function in c.functions: + if function.name.lower() in ["transfer","transferfrom"]: + continue + if CentralizedUtil.check_if_state_vars_read_from_critical_risk(function): + if function.visibility in ["public", "external"] and not function.view: + centralized_info_functions = CentralizedUtil.detect_function_if_centralized(function) + for centralized_info_function in centralized_info_functions: + if centralized_info_function['oz_read_or_written'] or \ + centralized_info_function['function_modifier_info']: + function_info = CentralizedUtil.output_function_centralized_info(function) + contract_info.append(self.generate_result(["\t- ", function, "\n"])) + results.extend(contract_info) if contract_info else None + return results + diff --git a/slither/detectors/functions/centralized_medium.py b/slither/detectors/functions/centralized_medium.py new file mode 100644 index 000000000..dabf7afab --- /dev/null +++ b/slither/detectors/functions/centralized_medium.py @@ -0,0 +1,78 @@ +""" +Module detecting modifiers that are not guaranteed to execute _; or revert()/throw + +Note that require()/assert() are not considered here. Even if they +are in the outermost scope, they do not guarantee a revert, so a +default value can still be returned. +""" +import json +import json +from slither.core.declarations.solidity_variables import SolidityVariableComposed +from slither.core.expressions import CallExpression +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.cfg.node import NodeType +from slither.core.declarations import ( + Contract, + Pragma, + Import, + Function, + Modifier, +) +from slither.core.declarations.event import Event +from slither.core.declarations import FunctionContract, Modifier +from slither.core.declarations import ( + SolidityFunction, +) +from slither.core.solidity_types.elementary_type import ElementaryType +from slither.detectors.functions.centralized_utils import CentralizedUtil + +from slither.slithir.operations import SolidityCall,InternalCall +from slither.slithir.operations.binary import Binary +from slither.slithir.operations.index import Index +from slither.slithir.operations.binary import BinaryType +from slither.detectors.functions.modifier_utils import ModifierUtil + + + + + +class CentralizedRiskMEDIUM(AbstractDetector): + """ + Detector for modifiers that return a default value + """ + + ARGUMENT = "centralized-risk-medium" + HELP = "Modifiers that can return the default value" + IMPACT = DetectorClassification.MEDIUM + CONFIDENCE = DetectorClassification.HIGH + WIKI = " " + + WIKI_TITLE = "Centralized Risk with function transfer Token" + WIKI_DESCRIPTION = "aaa" + + # region wiki_exploit_scenario + WIKI_EXPLOIT_SCENARIO = """""" + # endregion wiki_exploit_scenario + + WIKI_RECOMMENDATION = "" + + + def _detect(self): + results = [] + contract_info=[] + for c in self.contracts: + # contract_info = ["centralized risk found in ", c, '\n'] + + for function in c.functions: + if function.name.lower() in ["transfer","transferfrom"]: + continue + if CentralizedUtil.check_function_type_if_critical_risk(function): + if function.visibility in ["public", "external"] and not function.view: + centralized_info_functions = CentralizedUtil.detect_function_if_centralized(function) + for centralized_info_function in centralized_info_functions: + if centralized_info_function['oz_read_or_written'] or \ + centralized_info_function['function_modifier_info']: + function_info = CentralizedUtil.output_function_centralized_info(function) + contract_info.append(self.generate_result(["\t- ", function, "\n"])) + results.extend(contract_info) if contract_info else None + return results diff --git a/slither/detectors/functions/centralized_other.py b/slither/detectors/functions/centralized_other.py new file mode 100644 index 000000000..c24f61e65 --- /dev/null +++ b/slither/detectors/functions/centralized_other.py @@ -0,0 +1,76 @@ +""" +Module detecting modifiers that are not guaranteed to execute _; or revert()/throw + +Note that require()/assert() are not considered here. Even if they +are in the outermost scope, they do not guarantee a revert, so a +default value can still be returned. +""" +import json +import json +from slither.core.declarations.solidity_variables import SolidityVariableComposed +from slither.core.expressions import CallExpression +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.cfg.node import NodeType +from slither.core.declarations import ( + Contract, + Pragma, + Import, + Function, + Modifier, +) +from slither.core.declarations.event import Event +from slither.core.declarations import FunctionContract, Modifier +from slither.core.declarations import ( + SolidityFunction, +) +from slither.core.solidity_types.elementary_type import ElementaryType +from slither.detectors.functions.centralized_utils import CentralizedUtil + +from slither.slithir.operations import SolidityCall,InternalCall +from slither.slithir.operations.binary import Binary +from slither.slithir.operations.index import Index +from slither.slithir.operations.binary import BinaryType +from slither.detectors.functions.modifier_utils import ModifierUtil + + + + + +class CentralizedRiskOther(AbstractDetector): + """ + Detector for modifiers that return a default value + """ + + ARGUMENT = "centralized-risk-other" + HELP = "Modifiers that can return the default value" + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH + WIKI = " " + + WIKI_TITLE = "Centralized Risk with function transfer and transferFrom" + WIKI_DESCRIPTION = "aaa" + + # region wiki_exploit_scenario + WIKI_EXPLOIT_SCENARIO = """""" + # endregion wiki_exploit_scenario + + WIKI_RECOMMENDATION = "" + + + def _detect(self): + results = [] + contract_info=[] + for c in self.contracts: + for function in c.functions: + if function.name.lower() in ["transfer","transferfrom"]: + continue + if CentralizedUtil.check_if_function_other(function): + if function.visibility in ["public", "external"] and not function.view: + centralized_info_functions = CentralizedUtil.detect_function_if_centralized(function) + for centralized_info_function in centralized_info_functions: + if centralized_info_function['oz_read_or_written'] or \ + centralized_info_function['function_modifier_info']: + function_info = CentralizedUtil.output_function_centralized_info(function) + contract_info.append(self.generate_result(["\t- ", function, "\n"])) + results.extend(contract_info) if contract_info else None + return results diff --git a/slither/detectors/functions/centralized_utils.py b/slither/detectors/functions/centralized_utils.py new file mode 100644 index 000000000..7f3b3b801 --- /dev/null +++ b/slither/detectors/functions/centralized_utils.py @@ -0,0 +1,317 @@ +# -*- coding:utf-8 -*- +import time +from slither.core.declarations import FunctionContract, SolidityVariableComposed, Modifier +from slither.core.expressions import CallExpression +import json +from slither.core.declarations.solidity_variables import SolidityVariableComposed +from slither.core.expressions import CallExpression +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.cfg.node import NodeType +from slither.core.declarations import ( + Contract, + Pragma, + Import, + Function, + Modifier, +) +from slither.core.declarations.event import Event +from slither.core.declarations import FunctionContract, Modifier +from slither.core.declarations import ( + SolidityFunction, +) +from slither.core.solidity_types.elementary_type import ElementaryType + +from slither.slithir.operations import SolidityCall, InternalCall +from slither.slithir.operations.binary import Binary +from slither.slithir.operations.index import Index +from slither.slithir.operations.binary import BinaryType +from slither.slithir.operations.send import Send +from slither.slithir.operations.transfer import Transfer +from slither.detectors.functions.modifier_utils import ModifierUtil + +class CentralizedUtil: + + openzeppelin_contracts = [ + ('AccessControl','mapping(bytes32 => RoleData)','_roles'), + ('AccessControl','mapping(bytes32 => AccessControl.RoleData)','_roles'), + ('AccessControlEnumerable','mapping(bytes32 => EnumerableSet.AddressSet)','_roleMembers'), + ('Ownable','address','_owner'), + ('AccessControlUpgradeable','mapping(bytes32 => RoleData)','_roles'), + ('AccessControlUpgradeable','mapping(bytes32 => AccessControlUpgradeable.RoleData)','_roles'), + ('AccessControlEnumerableUpgradeable','mapping(bytes32 => EnumerableSetUpgradeable.AddressSet)','_roleMembers'), + ('OwnableUpgradeable','address','_owner'), + ('Pausable','bool','_paused'), + ('PausableUpgradeable','bool','_paused'), + ] + openzeppelin_modifiers=[ + 'onlyowner','onlyrole','whennotpaused','whenpaused','onlyadmin','only' + ] + + @staticmethod + def detect_inheritance(c: Contract): + ''' + Detects whether the contract inherits from contracts in OpenZeppelin and returns the variables used. + ''' + inheritance = c.inheritance + oz_var_usage = {} + oz_inheritance = [ + oz_contract + for c in inheritance + for oz_contract in CentralizedUtil.openzeppelin_contracts + if c.name == oz_contract[0] + ] + # Check for usage of variables defined in OpenZeppelin contracts and add them to oz_var_usage + for oz in oz_inheritance: + for svar in c.all_state_variables_read + c.all_state_variables_written: + if str(svar.type) == oz[1] and svar.name == oz[2]: + oz_var_usage[svar] = {'var_name':svar.name,'contract_name': oz[0], 'var_type': oz[1]} + return oz_var_usage + + @staticmethod + def detect_function_oz_usage(function:Function,oz_var_usage): + ''' + Detects the usage or writing of variables from OpenZeppelin within a function. + ''' + ret_function_read_or_write=[] + ret_function_write=[] + # Functions using variables from OpenZeppelin might have centralized restrictions + for var in function.state_variables_read+function.state_variables_written: + if var in oz_var_usage.keys() and var not in ret_function_read_or_write: + ret_function_read_or_write.append({var:oz_var_usage[var]}) + return ret_function_read_or_write + + @staticmethod + def detect_modifiers_common(contract:Contract): + ''' + Detects the usage of common modifiers (typically from OpenZeppelin) in the contract. + ''' + ret=[] + for m in contract.modifiers: + # Check if the modifier name matches any in openzeppelin_modifiers after processing + for om in CentralizedUtil.openzeppelin_modifiers: + if str(m.name).lower().replace('_','') == om or str(m.name).lower() == om or om in str(m.name).lower() or om in str(m.name).lower().replace('_',''): + # Find state variable reads within this modifier + ret.append({om:{'modifier':m}}) + return ret + + @staticmethod + def check_modifier_if_read_state_variable(modifier:Modifier): + ''' + Checks if the modifier reads state variables. + ''' + ret=[] + # Recursively find all calls within the modifier and its subcalls + def find_calls(modifier:Modifier): + ret=[] + for call in modifier.calls: + if call.node_type == NodeType.MODIFIER: + ret.append(call) + ret+=find_calls(call) + return ret + return ret + + @staticmethod + def detect_specific_modifier(contract:Contract): + ''' + Detects the usage of user-defined modifiers (non-OpenZeppelin) in the contract. + ''' + ret=[] + # Iterate over all modifiers, excluding those in openzeppelin_modifiers, similar to how detect_modifiers_common works + for m in contract.modifiers: + + for om in CentralizedUtil.openzeppelin_modifiers: + if not (str(m.name).lower().replace('_','') == om or str(m.name).lower() == om): + # Find state variables read or written within this modifier, specifically for address types + for var in m.state_variables_read+m.state_variables_written: + if var.type == ElementaryType('address') and var not in ret: + ret.append({var:{'var_name':var.name,'modifier':m,'var_type':var.type}}) + for var in m.variables_read: + if hasattr(var,"type") and var.type == ElementaryType('address') and var.name=='msg.sender' and var not in ret: + ret.append({var:{'var_name':var.name,'modifier':m,'var_type':var.type}}) + return ret + + @staticmethod + def detect_function_if_centralized(function:Function): + ret=[] + # A special modifier 'whennotpaused' might not necessarily denote a centralized function if it's unrelated to pausing operations + if 'whennotpaused' in [str(m.name).lower() for m in function.modifiers] and \ + 'pause' not in function.name.lower(): + return ret + + oz_var_usage = CentralizedUtil.detect_inheritance(function.contract) + oz_read_or_written=CentralizedUtil.detect_function_oz_usage(function,oz_var_usage) + function_modifier_info=[] + contract_common_modifiers_info=CentralizedUtil.detect_modifiers_common(function.contract) + contract_specific_modifiers_info=CentralizedUtil.detect_specific_modifier(function.contract) + function_unmodifier_check=CentralizedUtil.detect_function_unmodifier_check(function) + for info in contract_common_modifiers_info: + for om in info.keys(): + if info[om]['modifier'] in function.modifiers and info not in function_modifier_info: + function_modifier_info.append(info) + for info in contract_specific_modifiers_info: + for var in info.keys(): + if info[var]['modifier'] in function.modifiers and info not in function_modifier_info: + function_modifier_info.append(info) + for info in function_unmodifier_check: + for var in info.keys(): + if info not in function_modifier_info: + function_modifier_info.append(info) + if oz_read_or_written or function_modifier_info: + ret.append({'function':function,'oz_read_or_written':oz_read_or_written,'function_modifier_info':function_modifier_info}) + return ret + + @staticmethod + def detect_function_unmodifier_check(function: Function): + ret = [] + + def process_statement(s): + """Processes statements to extract relevant information.""" + for var in s.state_variables_read: + ret.append({var: {'var_name': var.name, 'node': s, 'var_type': var.type}}) + + for s in function.nodes: + # Check for 'require' or 'IF', and 'msg.sender' or 'msgsender' + if ('require' in str(s) or 'IF' in str(s)) and ("msg.sender" in str(s).lower() or "msgsender" in str(s).lower()): + contains_msg_sender_call = False + + # Iterate over all calls within this statement + for call in s.calls_as_expression: + if call.called and hasattr(call.called, "value") and isinstance(call.called.value, FunctionContract): + if any(isinstance(var, SolidityVariableComposed) for var in call.called.value.variables_read): + contains_msg_sender_call = True + break + + if contains_msg_sender_call or any(isinstance(vars_read, SolidityVariableComposed) for vars_read in s.variables_read): + process_statement(s) + + return ret + + @staticmethod + def output_function_centralized_info(function: Function): + info = {} + + info["function_name"] = function.name + + in_file = function.contract.source_mapping.filename.absolute + # Retrieve the source code + in_file_str = function.contract.compilation_unit.core.source_code[in_file] + + # Get the string + start = function.source_mapping.start + stop = start + function.source_mapping.length + func_source = in_file_str[start:stop] + + info["function_source"] = func_source + info["function_head"] = function.full_name + + address_vars_read = [] + for var in function.state_variables_read: + if var.type == ElementaryType('address'): + address_vars_read.append(var.name) + info["address_vars_read"] = address_vars_read + + address_vars_read_in_modifiers = [] + for m in function.modifiers: + for var in m.state_variables_read: + if var.type == ElementaryType('address'): + address_vars_read_in_modifiers.append(var.name) + info["address_vars_read_in_modifiers"] = address_vars_read_in_modifiers + + state_vars_written = [] + for var in function.state_variables_written: + state_vars_written.append(var.name) + info["state_vars_written"] = state_vars_written + + return info + + @staticmethod + def detect_function_if_transfer(function:Function): + for s in function.nodes: + if 'set' in function.name.lower() or 'add' in function.name.lower(): + return False + if 'call.value' in str(s) or 'transfer' in str(s) or 'send' in str(s): + return True + return False + + _function_risk_cache = {} + @staticmethod + def _get_cached_result(func, risk_type): + if func in CentralizedUtil._function_risk_cache: + return CentralizedUtil._function_risk_cache[func].get(risk_type) + return None + + @staticmethod + def _set_cached_result(func, risk_type, result): + if func not in CentralizedUtil._function_risk_cache: + CentralizedUtil._function_risk_cache[func] = {} + CentralizedUtil._function_risk_cache[func][risk_type] = result + + @staticmethod + def check_function_type_if_critical_risk(func:FunctionContract): + cached_result = CentralizedUtil._get_cached_result(func, 'critical') + if cached_result is not None: + return cached_result + + for node in func.nodes: + if any(isinstance(ir,Send) or isinstance(ir,Transfer) for ir in node.irs) or \ + 'call.value' in str(node) or \ + 'call{value' in str(node) or \ + any(hasattr(ir,'function_name') and str(ir.function_name).lower() in ['transferfrom','transfer'] for ir in node.irs): + CentralizedUtil._set_cached_result(func, 'critical', True) + return True + + CentralizedUtil._set_cached_result(func, 'critical', False) + return False + + @staticmethod + def check_if_state_vars_read_from_critical_risk(func:FunctionContract): + cached_result = CentralizedUtil._get_cached_result(func, 'high') + if cached_result is not None: + return cached_result + + state_vars_in_critical=[] + for f in func.contract.functions: + if f is func: + continue + if CentralizedUtil.check_function_type_if_critical_risk(f): + state_vars_in_critical.extend(f.state_variables_read) + for var in state_vars_in_critical: + if var in func.state_variables_written: + CentralizedUtil._set_cached_result(func, 'high', True) + return True + + CentralizedUtil._set_cached_result(func, 'high', False) + return False + + @staticmethod + def check_if_function_change_key_state(func:FunctionContract): + cached_result = CentralizedUtil._get_cached_result(func, 'medium') + if cached_result is not None: + return cached_result + + if CentralizedUtil.check_if_state_vars_read_from_critical_risk(func) or \ + CentralizedUtil.check_function_type_if_critical_risk(func): + CentralizedUtil._set_cached_result(func, 'medium', False) + return False + + if len(func.state_variables_written) > 0: + CentralizedUtil._set_cached_result(func, 'medium', True) + return True + + CentralizedUtil._set_cached_result(func, 'medium', False) + return False + + @staticmethod + def check_if_function_other(func:FunctionContract): + cached_result = CentralizedUtil._get_cached_result(func, 'low') + if cached_result is not None: + return cached_result + + if CentralizedUtil.check_if_state_vars_read_from_critical_risk(func) or \ + CentralizedUtil.check_function_type_if_critical_risk(func) or \ + CentralizedUtil.check_if_function_change_key_state(func): + CentralizedUtil._set_cached_result(func, 'low', False) + return False + + CentralizedUtil._set_cached_result(func, 'low', True) + return True diff --git a/slither/detectors/functions/modifier_utils.py b/slither/detectors/functions/modifier_utils.py new file mode 100644 index 000000000..54a61030f --- /dev/null +++ b/slither/detectors/functions/modifier_utils.py @@ -0,0 +1,80 @@ +# -*- coding:utf-8 -*- +from slither.core.declarations import FunctionContract, SolidityVariableComposed, Modifier +from slither.core.expressions import CallExpression + + +class ModifierUtil: + + @staticmethod + def is_reentrancy_lock(modifier: Modifier) -> bool: + """ + Whether it is a reentrancy lock + 1. There are read and write operations on state variables + 2. There are state variable condition statements (require, if) + 3. There is a placeholder: "_" + """ + if len(modifier.state_variables_read) <= 0 or len(modifier.state_variables_written) <= 0: + return False + + if not any(node.type.name == 'PLACEHOLDER' for node in modifier.nodes): + return False + + return len(modifier.all_conditional_state_variables_read(include_loop=True)) > 0 + + @staticmethod + def _get_function_variables_read_recursively(func: FunctionContract, max_depth=10): + variables_read = func.variables_read + if max_depth <= 0: + return variables_read + if len(func.calls_as_expressions) > 0: + for call in func.calls_as_expressions: + if isinstance(call, CallExpression) and \ + call.called and hasattr(call.called, 'value') and \ + isinstance(call.called.value, FunctionContract): + variables_read.extend(ModifierUtil._get_function_variables_read_recursively(call.called.value, max_depth=max_depth - 1)) + return variables_read + + @staticmethod + def _has_msg_sender_check_new(func: FunctionContract): + for modifier in func.modifiers: + for var in ModifierUtil._get_function_variables_read_recursively(modifier): + if isinstance(var, SolidityVariableComposed) and var.name == 'msg.sender': + return True + for var in ModifierUtil._get_function_variables_read_recursively(func): + if isinstance(var, SolidityVariableComposed) and var.name == 'msg.sender': + return True + return False + + @staticmethod + def _has_msg_sender_check(func: FunctionContract): + + for var in ModifierUtil._get_function_variables_read_recursively(func): + if isinstance(var, SolidityVariableComposed) and var.name == 'msg.sender': + return True + return False + + @staticmethod + def is_access_control(modifier: Modifier) -> bool: + """ + Whether there is access control (onlyXXX) + 1. There is a placeholder: "_" + 2. There are read and write operations on state variables + 3. There is a call statement containing msg.sender (require, if) + """ + + # No placeholder exists + if not any(node.type.name == 'PLACEHOLDER' for node in modifier.nodes): + return False + # There are read and write operations on state variables + if len(modifier.all_conditional_state_variables_read(include_loop=True)) < 0: + return False + if not ModifierUtil._has_msg_sender_check(modifier): + return False + return True + + @staticmethod + def check_all_modifiers_if_access_controll(func: FunctionContract): + for mod in func.modifiers: + if ModifierUtil.is_access_control(mod): + return True + return False diff --git a/slither/detectors/functions/transaction_order_dependency_high.py b/slither/detectors/functions/transaction_order_dependency_high.py new file mode 100644 index 000000000..902bd5c3e --- /dev/null +++ b/slither/detectors/functions/transaction_order_dependency_high.py @@ -0,0 +1,100 @@ +import itertools +from typing import List + +from slither.core.declarations.function_contract import FunctionContract +from slither.core.variables.state_variable import StateVariable +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.function_dependency_tree import build_dependency_tree_token_flow_or_money_flow +from slither.utils.function_permission_check import function_has_caller_check, function_can_only_initialized_once +from slither.detectors.functions.modifier_utils import ModifierUtil + + +class TransactionOrderDependencyHigh(AbstractDetector): # pylint: disable=too-few-public-methods + """ + Documentation + """ + + ARGUMENT = "transaction-order-dependency-high" # falcon will launch the detector with slither.py --mydetector + HELP = "check race conditions among contract functions (namely transaction order dependency)" + IMPACT = DetectorClassification.CRITICAL + CONFIDENCE = DetectorClassification.HIGH + + WIKI = """https://swcregistry.io/docs/SWC-114""" + + WIKI_TITLE = "transaction order dependency relevant to money flow or token flow" + WIKI_DESCRIPTION = "transaction-order-dependency plugin" + WIKI_EXPLOIT_SCENARIO = ".." + WIKI_RECOMMENDATION = ".." + ERC20_FUNCTION = [ + "transferFrom", + "safeTransferFrom", + "mint", + "burn", + "burnFrom", + "approve", + "balanceOf", + "totalSupply", + "transfer", + "allowance", + "safeTransfer", + "safeApprove", + "getReserve", + "transfer", + "balance" +] + def analyzeTOD(self, fns: List[FunctionContract]): + results = [] + + def analyzePairFunction(fn1: FunctionContract, fn2: FunctionContract): + if function_has_caller_check(fn1) and function_has_caller_check(fn2): + return False, [] + if fn1.canonical_name in self.money_flow_strong_dependency_tree: + if fn2 in self.money_flow_strong_dependency_tree[fn1.canonical_name]: + return True, set(fn1.all_state_variables_read()).intersection( + set(fn2.all_state_variables_written())) + + if fn2.canonical_name in self.money_flow_strong_dependency_tree: + if fn1 in self.money_flow_strong_dependency_tree[fn2.canonical_name]: + return True, set(fn2.all_state_variables_read()).intersection( + set(fn1.all_state_variables_written())) + + return False, [] + + for com in itertools.combinations(range(len(fns)), 2): + check, vars = analyzePairFunction(fns[com[0]], fns[com[1]]) + if check: + results.append([fns[com[0]], fns[com[1]], vars]) + return results + + def _detect(self): + results = [] + for contract in self.contracts: + if not contract.is_interface: + permissionless_functions = list() + for fn in contract.functions: + if ModifierUtil._has_msg_sender_check_new(fn) or len(fn.modifiers) > 0 or fn.visibility in ["private", "internal"]: + continue + if fn.view or fn.pure or fn.is_fallback or \ + fn.is_constructor or fn.is_constructor_variables or "init" in fn.name or "set" in fn.name \ + or len(fn.modifiers)>0 or any(ERC20_NAME in fn.name for ERC20_NAME in self.ERC20_FUNCTION)\ + or len(fn.state_variables_written) <= 0: + continue + if not function_can_only_initialized_once(fn): + permissionless_functions.append(fn) + if len(permissionless_functions) <= 1: + continue + self.money_flow_weak_dependency_tree, self.money_flow_strong_dependency_tree = build_dependency_tree_token_flow_or_money_flow( + contract) + todresults = self.analyzeTOD(fns=permissionless_functions) + for tod in todresults: + fn1: FunctionContract = tod[0] + fn2: FunctionContract = tod[1] + _vars: List[StateVariable] = tod[2] + info = [fn1.full_name, " and ", fn2.full_name, + " have transaction order dependency caused by data race on state variables:", + ", ".join([_var.canonical_name for _var in _vars]), "\n"] + info += ["\t- ", fn1, "\n"] + info += ["\t- ", fn2, "\n"] + res = self.generate_result(info) + results.append(res) + return results diff --git a/slither/detectors/functions/transaction_order_dependency_low.py b/slither/detectors/functions/transaction_order_dependency_low.py new file mode 100644 index 000000000..96f316b54 --- /dev/null +++ b/slither/detectors/functions/transaction_order_dependency_low.py @@ -0,0 +1,82 @@ +import itertools +from typing import List +from slither.utils.function_dependency_tree import build_dependency_tree +from slither.core.declarations.function_contract import FunctionContract +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.variables.state_variable import StateVariable +from slither.utils.function_permission_check import function_can_only_initialized_once, function_has_caller_check +from slither.detectors.functions.modifier_utils import ModifierUtil + + +class TransactionOrderDependencyLow(AbstractDetector): # pylint: disable=too-few-public-methods + """ + Documentation + """ + + ARGUMENT = "transaction-order-dependency-low" # falcon will launch the detector with slither.py --mydetector + HELP = "check race conditions among contract functions (namely transaction order dependency)" + IMPACT = DetectorClassification.LOW + CONFIDENCE = DetectorClassification.MEDIUM + + WIKI = """https://swcregistry.io/docs/SWC-114""" + + WIKI_TITLE = "transaction order dependency (low risk)" + WIKI_DESCRIPTION = "transaction-order-dependency plugin" + WIKI_EXPLOIT_SCENARIO = ".." + WIKI_RECOMMENDATION = ".." + + def analyzeTOD(self, fns: List[FunctionContract]): + results = [] + + def analyzePairFunction(fn1: FunctionContract, fn2: FunctionContract): + if fn1.canonical_name in self.weak_dependency_tree: + if fn2 in self.weak_dependency_tree[fn1.canonical_name]: + return True, set(fn1.all_state_variables_read()).intersection( + set(fn2.all_state_variables_written())) + + if fn2.canonical_name in self.weak_dependency_tree: + if fn1 in self.weak_dependency_tree[fn2.canonical_name]: + return True, set(fn2.all_state_variables_read()).intersection( + set(fn1.all_state_variables_written())) + return False, [] + + for com in itertools.combinations(range(len(fns)), 2): + check, vars = analyzePairFunction(fns[com[0]], fns[com[1]]) + if check: + results.append([fns[com[0]], fns[com[1]], vars]) + return results + + def _detect(self): + + info = "transaction order dependency detector" + results = [] + for contract in self.contracts: + if not contract.is_interface: + permissionless_functions = list() + for fn in contract.functions: + if ModifierUtil._has_msg_sender_check_new(fn) or len(fn.modifiers) > 0 or fn.visibility in ["private", "internal"]: + continue + if fn.view or fn.pure or fn.is_fallback \ + or len(fn.state_variables_written) <= 0 \ + or fn.is_constructor or fn.is_constructor_variables \ + or function_has_caller_check(fn) or function_can_only_initialized_once(fn): + continue + permissionless_functions.append(fn) + + if len(permissionless_functions) <= 1: + continue + self.weak_dependency_tree, self.strong_dependency_tree = build_dependency_tree(contract) + tod_results = self.analyzeTOD(fns=permissionless_functions) + + for tod in tod_results: + fn1: FunctionContract = tod[0] + fn2: FunctionContract = tod[1] + _vars: List[StateVariable] = tod[2] + info = [fn1.full_name, " and ", fn2.full_name, + " have transaction order dependency caused by data race on state variables:", + ", ".join([_var.canonical_name for _var in _vars]), "\n"] + info += ["\t- ", fn1, "\n"] + info += ["\t- ", fn2, "\n"] + res = self.generate_result(info) + results.append(res) + return results diff --git a/slither/detectors/functions/transaction_order_dependency_medium.py b/slither/detectors/functions/transaction_order_dependency_medium.py new file mode 100644 index 000000000..8e8acc69d --- /dev/null +++ b/slither/detectors/functions/transaction_order_dependency_medium.py @@ -0,0 +1,104 @@ +import itertools +from typing import List +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.declarations.function_contract import FunctionContract +from slither.core.variables.state_variable import StateVariable +from slither.utils.function_permission_check import function_can_only_initialized_once, function_has_caller_check +from slither.utils.function_dependency_tree import build_dependency_tree_token_flow_or_money_flow, build_dependency_tree +from slither.detectors.functions.modifier_utils import ModifierUtil + +class TransactionOrderDependencyMedium(AbstractDetector): # pylint: disable=too-few-public-methods + """ + Documentation + """ + ARGUMENT = "transaction-order-dependency-medium" # falcon will launch the detector with slither.py --mydetector + HELP = "check race conditions among contract functions (namely transaction order dependency)" + IMPACT = DetectorClassification.MEDIUM + CONFIDENCE = DetectorClassification.HIGH + + WIKI = """https://swcregistry.io/docs/SWC-114""" + + WIKI_TITLE = "transaction order dependency (medium risk)" + WIKI_DESCRIPTION = "transaction-order-dependency plugin" + WIKI_EXPLOIT_SCENARIO = ".." + WIKI_RECOMMENDATION = ".." + + def analyzeTOD(self, fns: List[FunctionContract]): + results = [] + + def analyzePairFunction(fn1: FunctionContract, fn2: FunctionContract): + if function_has_caller_check(fn1) and function_has_caller_check(fn2): + return False, [] + if fn1.canonical_name in self.money_flow_strong_dependency_tree: + if fn2 in self.money_flow_strong_dependency_tree[fn1.canonical_name]: + return False, [] + + if fn2.canonical_name in self.money_flow_strong_dependency_tree: + if fn1 in self.money_flow_strong_dependency_tree[fn2.canonical_name]: + return False, [] + + if fn1.canonical_name in self.money_flow_weak_dependency_tree: + if fn2 in self.money_flow_weak_dependency_tree[fn1.canonical_name]: + return True, set(fn1.all_state_variables_read()).intersection( + set(fn2.all_state_variables_written())) + + if fn2.canonical_name in self.money_flow_weak_dependency_tree: + if fn1 in self.money_flow_weak_dependency_tree[fn2.canonical_name]: + return True, set(fn2.all_state_variables_read()).intersection( + set(fn1.all_state_variables_written())) + + if fn1.canonical_name in self.strong_dependency_tree: + if fn2 in self.strong_dependency_tree[fn1.canonical_name]: + return True, set(fn1.all_state_variables_read()).intersection( + set(fn2.all_state_variables_written())) + + if fn2.canonical_name in self.strong_dependency_tree: + if fn1 in self.strong_dependency_tree[fn2.canonical_name]: + return True, set(fn2.all_state_variables_read()).intersection( + set(fn1.all_state_variables_written())) + + return False, [] + + for com in itertools.combinations(range(len(fns)), 2): + check, vars = analyzePairFunction(fns[com[0]], fns[com[1]]) + if check: + results.append([fns[com[0]], fns[com[1]], vars]) + return results + + def _detect(self): + + info = "transaction order dependency detector" + results = [] + for contract in self.contracts: + if not contract.is_interface: + permissionless_functions = list() + for fn in contract.functions: + if ModifierUtil._has_msg_sender_check_new(fn) or len(fn.modifiers) > 0 or fn.visibility in ["private", "internal"]: + continue + if fn.view or fn.pure or fn.is_fallback \ + or len(fn.state_variables_written) <= 0 \ + or fn.is_constructor or fn.is_constructor_variables \ + or function_can_only_initialized_once(fn): + continue + permissionless_functions.append(fn) + + if len(permissionless_functions) <= 1: + continue + self.money_flow_weak_dependency_tree, self.money_flow_strong_dependency_tree = \ + build_dependency_tree_token_flow_or_money_flow(contract) + + self.weak_dependency_tree, self.strong_dependency_tree = build_dependency_tree(contract) + + todresults = self.analyzeTOD(fns=permissionless_functions) + for tod in todresults: + fn1: FunctionContract = tod[0] + fn2: FunctionContract = tod[1] + _vars: List[StateVariable] = tod[2] + info = [fn1.full_name, " and ", fn2.full_name, + " have transaction order dependency caused by data race on state variables:", + ", ".join([_var.canonical_name for _var in _vars]), "\n"] + info += ["\t- ", fn1, "\n"] + info += ["\t- ", fn2, "\n"] + res = self.generate_result(info) + results.append(res) + return results diff --git a/slither/utils/function_dependency_tree.py b/slither/utils/function_dependency_tree.py new file mode 100644 index 000000000..e762f8276 --- /dev/null +++ b/slither/utils/function_dependency_tree.py @@ -0,0 +1,220 @@ +import copy +import itertools +from typing import List, Optional, Set +from falcon.core.cfg.node import Node + +from falcon.core.declarations import Contract +from falcon.core.declarations.function import Function +from falcon.core.declarations.function_contract import FunctionContract +from falcon.core.declarations.solidity_variables import SolidityFunction + +class FunctionUtil: + def __init__(self, fn: FunctionContract): + self.fn = fn + self.read = self.fn.all_state_variables_read() + self.written = self.fn.all_state_variables_written() + + def is_rw(self, statevar): + if not (statevar in self.read and statevar in self.written): + return False + else: + all_nodes = self.fn.all_nodes() + processed = list() + cur_node = self.fn.entry_point + to_process = list() + to_process.append(cur_node) + while len(to_process)>0: + node: Node = to_process.pop() + if node in all_nodes and node not in processed: + read = node.state_variables_read + written = node.state_variables_written + if statevar in read: + return True + else: + if statevar in written: + return False + else: + continue + processed.append(node) + for son in node.sons: + if son not in processed: + to_process.extend(node.sons) + for fn in node.internal_calls: + if isinstance(fn, Function) and not isinstance(fn, SolidityFunction): + if fn.entry_point not in processed: + to_process.append(fn.entry_point) + return False + + def is_r(self, statevar): + if not (statevar in self.read and statevar not in self.written): + return False + else: + return True + + def is_w(self, statevar): + if not (statevar in self.written): + return False + else: + if statevar not in self.read: + return True + else: + all_nodes = self.fn.all_nodes() + processed = list() + cur_node = self.fn.entry_point + to_process = list() + to_process.append(cur_node) + while len(to_process)>0: + node = to_process.pop() + if node in all_nodes and node not in processed: + read = node.state_variables_read + written = node.state_variables_written + if statevar in written and statevar not in read: + return True + else: + if statevar in read: + return False + else: + continue + processed.append(node) + for son in node.sons: + if son not in processed: + to_process.extend(node.sons) + for fn in node.internal_calls: + if isinstance(fn, Function) and not isinstance(fn, SolidityFunction): + if fn.entry_point not in processed: + to_process.append(fn.entry_point) + return False + + +def weak_depend(fn: FunctionContract, other_fn: FunctionContract): + statevars = fn.contract.state_variables + for statevar in statevars: + if not (FunctionUtil(fn).is_r(statevar) or FunctionUtil(fn).is_w(statevar) or FunctionUtil(fn).is_rw(statevar)): + continue + if ( + (FunctionUtil(fn).is_r(statevar) and FunctionUtil(other_fn).is_rw(statevar)) + or + (FunctionUtil(fn).is_rw(statevar) and FunctionUtil(other_fn).is_rw(statevar)) + ): + return True + + return False + +def strong_depend(fn: FunctionContract, other_fn: FunctionContract): + statevars = fn.contract.state_variables + for statevar in statevars: + if not (FunctionUtil(fn).is_r(statevar) or FunctionUtil(fn).is_w(statevar) or FunctionUtil(fn).is_rw(statevar)): + continue + if (FunctionUtil(fn).is_r(statevar) or FunctionUtil(fn).is_rw(statevar)) and FunctionUtil(other_fn).is_w(statevar): + return True + return False + +def build_dependency_tree(contract: Contract): + weak_dependency_tree = dict() + strong_dependency_tree = dict() + fns = [ fn for fn in contract.functions if fn.view == False and fn.pure == False and fn.is_constructor == False ] + for pair in itertools.combinations(range(len(fns)), 2): + fn_left, fn_right = fns[pair[0]], fns[pair[1]] + # print(fn_left, fn_right, strong_depend(fn_left, fn_right), strong_depend(fn_right, fn_left), weak_depend(fn_left, fn_right), weak_depend(fn_right, fn_left)) + if strong_depend(fn_left, fn_right): + if fn_left.canonical_name not in strong_dependency_tree: + strong_dependency_tree[fn_left.canonical_name] = [] + strong_dependency_tree[fn_left.canonical_name].append(fn_right) + elif weak_depend(fn_left, fn_right): + if fn_left.canonical_name not in weak_dependency_tree: + weak_dependency_tree[fn_left.canonical_name] = [] + weak_dependency_tree[fn_left.canonical_name].append(fn_right) + elif strong_depend(fn_right, fn_left): + if fn_right.canonical_name not in strong_dependency_tree: + strong_dependency_tree[fn_right.canonical_name] = [] + strong_dependency_tree[fn_right.canonical_name].append(fn_left) + elif weak_depend(fn_right, fn_left): + if fn_right.canonical_name not in weak_dependency_tree: + weak_dependency_tree[fn_right.canonical_name] = [] + weak_dependency_tree[fn_right.canonical_name].append(fn_left) + + return weak_dependency_tree, strong_dependency_tree + +ERC20_token_flow_fns = { + "transfer", + "transferFrom", +} +ERC223_token_flow_fns = { + "transfer" +} +ERC721_token_flow_fns = { + "safeTransferFrom", + "transferFrom" +} +ERC777_token_flow_fns = { + "send", + "operatorSend", + "burn", + "operatorBurn" +} +ERC1155_token_flow_fns = { + "safeTransferFrom", + "safeBatchTransferFrom", +} +ERC1363_token_flow_fns = { + "transferAndCall", + "transferFromAndCall" +}.union(ERC20_token_flow_fns) + +ERC4524_token_flow_fns = { + "safeTransfer", + "safeTransferFrom" +}.union(ERC20_token_flow_fns) + +ERC4626_token_flow_fns = { + "deposit", + "previewMint", + "mint", + "maxWithdraw", + "previewWithdraw", + "withdraw", + "maxRedeem", + "previewRedeem", + "redeem" +}.union(ERC20_token_flow_fns) + +ERCS = ERC20_token_flow_fns.union( + ERC223_token_flow_fns).union( + ERC223_token_flow_fns).union(ERC721_token_flow_fns).union( + ERC777_token_flow_fns).union(ERC1155_token_flow_fns).union( + ERC1363_token_flow_fns).union( + ERC4524_token_flow_fns).union( + ERC4626_token_flow_fns) + +def build_dependency_tree_token_flow_or_money_flow(contract:Contract): + weak_dependency_tree, strong_dependency_tree = build_dependency_tree(contract) + # print(weak_dependency_tree) + # print(strong_dependency_tree) + fns = [ fn for fn in contract.functions if fn.view == False and fn.pure == False \ + and (fn.can_send_eth() or fn.name in ERCS) ] + money_flow_weak_dependency_tree = dict() + money_flow_strong_dependency_tree = dict() + for fn in fns: + cur = fn + process_list = [cur] + processed = [] + while len(process_list) > 0: + cur = process_list.pop() + if cur not in processed and cur.canonical_name in weak_dependency_tree: + processed.append(cur) + money_flow_weak_dependency_tree[cur.canonical_name] = weak_dependency_tree[cur.canonical_name] + process_list.extend(money_flow_weak_dependency_tree[cur.canonical_name]) + + cur = fn + process_list = [cur] + processed = [] + while len(process_list) > 0: + cur = process_list.pop() + if cur not in processed and cur.canonical_name in strong_dependency_tree: + processed.append(cur) + money_flow_strong_dependency_tree[cur.canonical_name] = strong_dependency_tree[cur.canonical_name] + process_list.extend(money_flow_strong_dependency_tree[cur.canonical_name]) + + return money_flow_weak_dependency_tree, money_flow_strong_dependency_tree + + \ No newline at end of file diff --git a/slither/utils/function_permission_check.py b/slither/utils/function_permission_check.py new file mode 100644 index 000000000..1360f8f59 --- /dev/null +++ b/slither/utils/function_permission_check.py @@ -0,0 +1,64 @@ +from falcon.core.declarations.function_contract import FunctionContract +from falcon.core.cfg.node import NodeType +from falcon.core.declarations.solidity_variables import SolidityVariableComposed +from falcon.core.expressions.call_expression import CallExpression +from falcon.ir.operations import HighLevelCall, LibraryCall, Binary, BinaryType, Unary, UnaryType, Assignment +from falcon.ir.operations.codesize import CodeSize +from falcon.ir.variables.constant import Constant +from falcon.utils.modifier_utils import ModifierUtil + +def function_can_only_initialized_once(fn: FunctionContract): + if fn.name in ['initialize', 'init']: + return True + flag_vars = [] + for node in fn.nodes: + for ir in node.irs: + if isinstance(ir, Binary): + if BinaryType.return_bool(ir.type) and ir.type == BinaryType.NOT_EQUAL: + if isinstance(ir.variable_right, Constant) and ir.variable_left == True: + flag_vars.append(ir.variable_left) + elif isinstance(ir, Unary): + if UnaryType.get_type(str(ir.type).strip(), isprefix=True) == UnaryType.BANG: + flag_vars.append(ir.rvalue) + # if fn.contract.name == "OKOffChainExchange" and fn.name=="init": + # print(ir, ir.lvalue.name, ir.rvalue, type(ir.rvalue), list(map(lambda a: a.name, flag_vars))) + if len(flag_vars) > 0: + if isinstance(ir, Assignment): + if ir.lvalue.name in list(map(lambda a: a.name, flag_vars)) and isinstance(ir.rvalue, + Constant) and ir.rvalue.value == True: + return True + return False + +def function_has_caller_check(fn: FunctionContract): + hasContractCheck = False + if ModifierUtil._has_msg_sender_check_new(fn): + return True + if fn.is_constructor or fn.is_protected() or fn.pure or fn.view or (not (fn.visibility in ["public", "external"])): + return True + for node in fn.all_nodes(): + isContractChecks = [True for ir in node.irs if ( + isinstance(ir, HighLevelCall) or isinstance(ir, LibraryCall)) and ir.function_name == "isContract"] + if any(isContractChecks) is True: + hasContractCheck = True + break + codeSizingChecks = [True for ir in node.irs if isinstance(ir, CodeSize)] + if any(codeSizingChecks) is True: + hasContractCheck = True + break + + txOriginChecks = [True for ir in node.irs if isinstance(ir, Binary) and ir.type == BinaryType.EQUAL and ( + (str(ir.variable_left) == "tx.origin" and str(ir.variable_right) == "msg.sender") or ( + str(ir.variable_right) == "tx.origin" and str(ir.variable_left) == "msg.sender"))] + + if any(txOriginChecks) is True: + hasContractCheck = True + break + + if node.type == NodeType.ASSEMBLY: + inline_asm = node.inline_asm + if inline_asm: + if "extcodesize" in inline_asm: + hasContractCheck = True + break + + return hasContractCheck diff --git a/tests/e2e/detectors/test_data/centralized-init-supply/0.8.6/test.sol b/tests/e2e/detectors/test_data/centralized-init-supply/0.8.6/test.sol new file mode 100644 index 000000000..90479603a --- /dev/null +++ b/tests/e2e/detectors/test_data/centralized-init-supply/0.8.6/test.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.0 <0.9.0; + +contract TestContract { + struct BalancesStruct { + address owner; + uint256[] balances; + } + address owner; + mapping(address => uint256) public balances; + mapping (address => BalancesStruct) public stackBalance; + constructor() { + mint(); + balances[owner]+=1; + stackBalance[msg.sender].owner = msg.sender; + } + function getStateVar() public view returns (uint256) { + return stackBalance[msg.sender].balances.length; + } + function mint() public { + balances[msg.sender]+=1; + } +} diff --git a/tests/e2e/detectors/test_data/centralized-risk-medium/0.4.24/Hundred.sol b/tests/e2e/detectors/test_data/centralized-risk-medium/0.4.24/Hundred.sol new file mode 100644 index 000000000..0ba0e4c11 --- /dev/null +++ b/tests/e2e/detectors/test_data/centralized-risk-medium/0.4.24/Hundred.sol @@ -0,0 +1,1029 @@ +/** + *Submitted for verification at gnosisscan.io on 2022-08-05 +*/ + +// File: openzeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol + +pragma solidity ^0.4.24; + + +/** + * @title ERC20Basic + * @dev Simpler version of ERC20 interface + * See https://github.com/ethereum/EIPs/issues/179 + */ +contract ERC20Basic { + function totalSupply() public view returns (uint256); + function balanceOf(address _who) public view returns (uint256); + function transfer(address _to, uint256 _value) public returns (bool); + event Transfer(address indexed from, address indexed to, uint256 value); +} + +// File: openzeppelin-solidity/contracts/math/SafeMath.sol + +pragma solidity ^0.4.24; + + +/** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ +library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 _a, uint256 _b) internal pure returns (uint256 c) { + // Gas optimization: this is cheaper than asserting 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 + if (_a == 0) { + return 0; + } + + c = _a * _b; + assert(c / _a == _b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 _a, uint256 _b) internal pure returns (uint256) { + // assert(_b > 0); // Solidity automatically throws when dividing by 0 + // uint256 c = _a / _b; + // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold + return _a / _b; + } + + /** + * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 _a, uint256 _b) internal pure returns (uint256) { + assert(_b <= _a); + return _a - _b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 _a, uint256 _b) internal pure returns (uint256 c) { + c = _a + _b; + assert(c >= _a); + return c; + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/BasicToken.sol + +pragma solidity ^0.4.24; + + + + +/** + * @title Basic token + * @dev Basic version of StandardToken, with no allowances. + */ +contract BasicToken is ERC20Basic { + using SafeMath for uint256; + + mapping(address => uint256) internal balances; + + uint256 internal totalSupply_; + + /** + * @dev Total number of tokens in existence + */ + function totalSupply() public view returns (uint256) { + return totalSupply_; + } + + /** + * @dev Transfer token for a specified address + * @param _to The address to transfer to. + * @param _value The amount to be transferred. + */ + function transfer(address _to, uint256 _value) public returns (bool) { + require(_value <= balances[msg.sender]); + require(_to != address(0)); + + balances[msg.sender] = balances[msg.sender].sub(_value); + balances[_to] = balances[_to].add(_value); + emit Transfer(msg.sender, _to, _value); + return true; + } + + /** + * @dev Gets the balance of the specified address. + * @param _owner The address to query the the balance of. + * @return An uint256 representing the amount owned by the passed address. + */ + function balanceOf(address _owner) public view returns (uint256) { + return balances[_owner]; + } + +} + +// File: openzeppelin-solidity/contracts/token/ERC20/BurnableToken.sol + +pragma solidity ^0.4.24; + + + +/** + * @title Burnable Token + * @dev Token that can be irreversibly burned (destroyed). + */ +contract BurnableToken is BasicToken { + + event Burn(address indexed burner, uint256 value); + + /** + * @dev Burns a specific amount of tokens. + * @param _value The amount of token to be burned. + */ + function burn(uint256 _value) public { + _burn(msg.sender, _value); + } + + function _burn(address _who, uint256 _value) internal { + require(_value <= balances[_who]); + // no need to require value <= totalSupply, since that would imply the + // sender's balance is greater than the totalSupply, which *should* be an assertion failure + + balances[_who] = balances[_who].sub(_value); + totalSupply_ = totalSupply_.sub(_value); + emit Burn(_who, _value); + emit Transfer(_who, address(0), _value); + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/ERC20.sol + +pragma solidity ^0.4.24; + + + +/** + * @title ERC20 interface + * @dev see https://github.com/ethereum/EIPs/issues/20 + */ +contract ERC20 is ERC20Basic { + function allowance(address _owner, address _spender) + public view returns (uint256); + + function transferFrom(address _from, address _to, uint256 _value) + public returns (bool); + + function approve(address _spender, uint256 _value) public returns (bool); + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); +} + +// File: openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol + +pragma solidity ^0.4.24; + + + + +/** + * @title Standard ERC20 token + * + * @dev Implementation of the basic standard token. + * https://github.com/ethereum/EIPs/issues/20 + * Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol + */ +contract StandardToken is ERC20, BasicToken { + + mapping (address => mapping (address => uint256)) internal allowed; + + + /** + * @dev Transfer tokens from one address to another + * @param _from address The address which you want to send tokens from + * @param _to address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + */ + function transferFrom( + address _from, + address _to, + uint256 _value + ) + public + returns (bool) + { + require(_value <= balances[_from]); + require(_value <= allowed[_from][msg.sender]); + require(_to != address(0)); + + balances[_from] = balances[_from].sub(_value); + balances[_to] = balances[_to].add(_value); + allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); + emit Transfer(_from, _to, _value); + return true; + } + + /** + * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. + * Beware that changing an allowance with this method brings the risk that someone may use both the old + * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this + * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * @param _spender The address which will spend the funds. + * @param _value The amount of tokens to be spent. + */ + function approve(address _spender, uint256 _value) public returns (bool) { + allowed[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } + + /** + * @dev Function to check the amount of tokens that an owner allowed to a spender. + * @param _owner address The address which owns the funds. + * @param _spender address The address which will spend the funds. + * @return A uint256 specifying the amount of tokens still available for the spender. + */ + function allowance( + address _owner, + address _spender + ) + public + view + returns (uint256) + { + return allowed[_owner][_spender]; + } + + /** + * @dev Increase the amount of tokens that an owner allowed to a spender. + * approve should be called when allowed[_spender] == 0. To increment + * allowed value is better to use this function to avoid 2 calls (and wait until + * the first transaction is mined) + * From MonolithDAO Token.sol + * @param _spender The address which will spend the funds. + * @param _addedValue The amount of tokens to increase the allowance by. + */ + function increaseApproval( + address _spender, + uint256 _addedValue + ) + public + returns (bool) + { + allowed[msg.sender][_spender] = ( + allowed[msg.sender][_spender].add(_addedValue)); + emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); + return true; + } + + /** + * @dev Decrease the amount of tokens that an owner allowed to a spender. + * approve should be called when allowed[_spender] == 0. To decrement + * allowed value is better to use this function to avoid 2 calls (and wait until + * the first transaction is mined) + * From MonolithDAO Token.sol + * @param _spender The address which will spend the funds. + * @param _subtractedValue The amount of tokens to decrease the allowance by. + */ + function decreaseApproval( + address _spender, + uint256 _subtractedValue + ) + public + returns (bool) + { + uint256 oldValue = allowed[msg.sender][_spender]; + if (_subtractedValue >= oldValue) { + allowed[msg.sender][_spender] = 0; + } else { + allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue); + } + emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); + return true; + } + +} + +// File: openzeppelin-solidity/contracts/ownership/Ownable.sol + +pragma solidity ^0.4.24; + + +/** + * @title Ownable + * @dev The Ownable contract has an owner address, and provides basic authorization control + * functions, this simplifies the implementation of "user permissions". + */ +contract Ownable { + address public owner; + + + event OwnershipRenounced(address indexed previousOwner); + event OwnershipTransferred( + address indexed previousOwner, + address indexed newOwner + ); + + + /** + * @dev The Ownable constructor sets the original `owner` of the contract to the sender + * account. + */ + constructor() public { + owner = msg.sender; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + /** + * @dev Allows the current owner to relinquish control of the contract. + * @notice Renouncing to ownership will leave the contract without an owner. + * It will not be possible to call the functions with the `onlyOwner` + * modifier anymore. + */ + function renounceOwnership() public onlyOwner { + emit OwnershipRenounced(owner); + owner = address(0); + } + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param _newOwner The address to transfer ownership to. + */ + function transferOwnership(address _newOwner) public onlyOwner { + _transferOwnership(_newOwner); + } + + /** + * @dev Transfers control of the contract to a newOwner. + * @param _newOwner The address to transfer ownership to. + */ + function _transferOwnership(address _newOwner) internal { + require(_newOwner != address(0)); + emit OwnershipTransferred(owner, _newOwner); + owner = _newOwner; + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/MintableToken.sol + +pragma solidity ^0.4.24; + + + + +/** + * @title Mintable token + * @dev Simple ERC20 Token example, with mintable token creation + * Based on code by TokenMarketNet: https://github.com/TokenMarketNet/ico/blob/master/contracts/MintableToken.sol + */ +contract MintableToken is StandardToken, Ownable { + event Mint(address indexed to, uint256 amount); + event MintFinished(); + + bool public mintingFinished = false; + + + modifier canMint() { + require(!mintingFinished); + _; + } + + modifier hasMintPermission() { + require(msg.sender == owner); + _; + } + + /** + * @dev Function to mint tokens + * @param _to The address that will receive the minted tokens. + * @param _amount The amount of tokens to mint. + * @return A boolean that indicates if the operation was successful. + */ + function mint( + address _to, + uint256 _amount + ) + public + hasMintPermission + canMint + returns (bool) + { + totalSupply_ = totalSupply_.add(_amount); + balances[_to] = balances[_to].add(_amount); + emit Mint(_to, _amount); + emit Transfer(address(0), _to, _amount); + return true; + } + + /** + * @dev Function to stop minting new tokens. + * @return True if the operation was successful. + */ + function finishMinting() public onlyOwner canMint returns (bool) { + mintingFinished = true; + emit MintFinished(); + return true; + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol + +pragma solidity ^0.4.24; + + + +/** + * @title DetailedERC20 token + * @dev The decimals are only for visualization purposes. + * All the operations are done using the smallest and indivisible token unit, + * just as on Ethereum all the operations are done in wei. + */ +contract DetailedERC20 is ERC20 { + string public name; + string public symbol; + uint8 public decimals; + + constructor(string _name, string _symbol, uint8 _decimals) public { + name = _name; + symbol = _symbol; + decimals = _decimals; + } +} + +// File: openzeppelin-solidity/contracts/AddressUtils.sol + +pragma solidity ^0.4.24; + + +/** + * Utility library of inline functions on addresses + */ +library AddressUtils { + + /** + * Returns whether the target address is a contract + * @dev This function will return false if invoked during the constructor of a contract, + * as the code is not actually created until after the constructor finishes. + * @param _addr address to check + * @return whether the target address is a contract + */ + function isContract(address _addr) internal view returns (bool) { + uint256 size; + // XXX Currently there is no better way to check if there is a contract in an address + // than to check the size of the code at that address. + // See https://ethereum.stackexchange.com/a/14016/36603 + // for more details about how this works. + // TODO Check this again before the Serenity release, because all addresses will be + // contracts then. + // solium-disable-next-line security/no-inline-assembly + assembly { size := extcodesize(_addr) } + return size > 0; + } + +} + +// File: contracts/interfaces/ERC677.sol + +pragma solidity 0.4.24; + + +contract ERC677 is ERC20 { + event Transfer(address indexed from, address indexed to, uint256 value, bytes data); + + function transferAndCall(address, uint256, bytes) external returns (bool); + + function increaseAllowance(address spender, uint256 addedValue) public returns (bool); + function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool); +} + +contract LegacyERC20 { + function transfer(address _spender, uint256 _value) public; // returns (bool); + function transferFrom(address _owner, address _spender, uint256 _value) public; // returns (bool); +} + +// File: contracts/interfaces/IBurnableMintableERC677Token.sol + +pragma solidity 0.4.24; + + +contract IBurnableMintableERC677Token is ERC677 { + function mint(address _to, uint256 _amount) public returns (bool); + function burn(uint256 _value) public; + function claimTokens(address _token, address _to) external; +} + +// File: contracts/upgradeable_contracts/Sacrifice.sol + +pragma solidity 0.4.24; + +contract Sacrifice { + constructor(address _recipient) public payable { + selfdestruct(_recipient); + } +} + +// File: contracts/libraries/Address.sol + +pragma solidity 0.4.24; + + +/** + * @title Address + * @dev Helper methods for Address type. + */ +library Address { + /** + * @dev Try to send native tokens to the address. If it fails, it will force the transfer by creating a selfdestruct contract + * @param _receiver address that will receive the native tokens + * @param _value the amount of native tokens to send + */ + function safeSendValue(address _receiver, uint256 _value) internal { + if (!_receiver.send(_value)) { + (new Sacrifice).value(_value)(_receiver); + } + } +} + +// File: contracts/libraries/SafeERC20.sol + +pragma solidity 0.4.24; + + + +/** + * @title SafeERC20 + * @dev Helper methods for safe token transfers. + * Functions perform additional checks to be sure that token transfer really happened. + */ +library SafeERC20 { + using SafeMath for uint256; + + /** + * @dev Same as ERC20.transfer(address,uint256) but with extra consistency checks. + * @param _token address of the token contract + * @param _to address of the receiver + * @param _value amount of tokens to send + */ + function safeTransfer(address _token, address _to, uint256 _value) internal { + LegacyERC20(_token).transfer(_to, _value); + assembly { + if returndatasize { + returndatacopy(0, 0, 32) + if iszero(mload(0)) { + revert(0, 0) + } + } + } + } + + /** + * @dev Same as ERC20.transferFrom(address,address,uint256) but with extra consistency checks. + * @param _token address of the token contract + * @param _from address of the sender + * @param _value amount of tokens to send + */ + function safeTransferFrom(address _token, address _from, uint256 _value) internal { + LegacyERC20(_token).transferFrom(_from, address(this), _value); + assembly { + if returndatasize { + returndatacopy(0, 0, 32) + if iszero(mload(0)) { + revert(0, 0) + } + } + } + } +} + +// File: contracts/upgradeable_contracts/Claimable.sol + +pragma solidity 0.4.24; + + + +/** + * @title Claimable + * @dev Implementation of the claiming utils that can be useful for withdrawing accidentally sent tokens that are not used in bridge operations. + */ +contract Claimable { + using SafeERC20 for address; + + /** + * Throws if a given address is equal to address(0) + */ + modifier validAddress(address _to) { + require(_to != address(0)); + /* solcov ignore next */ + _; + } + + /** + * @dev Withdraws the erc20 tokens or native coins from this contract. + * Caller should additionally check that the claimed token is not a part of bridge operations (i.e. that token != erc20token()). + * @param _token address of the claimed token or address(0) for native coins. + * @param _to address of the tokens/coins receiver. + */ + function claimValues(address _token, address _to) internal validAddress(_to) { + if (_token == address(0)) { + claimNativeCoins(_to); + } else { + claimErc20Tokens(_token, _to); + } + } + + /** + * @dev Internal function for withdrawing all native coins from the contract. + * @param _to address of the coins receiver. + */ + function claimNativeCoins(address _to) internal { + uint256 value = address(this).balance; + Address.safeSendValue(_to, value); + } + + /** + * @dev Internal function for withdrawing all tokens of ssome particular ERC20 contract from this contract. + * @param _token address of the claimed ERC20 token. + * @param _to address of the tokens receiver. + */ + function claimErc20Tokens(address _token, address _to) internal { + ERC20Basic token = ERC20Basic(_token); + uint256 balance = token.balanceOf(this); + _token.safeTransfer(_to, balance); + } +} + +// File: contracts/ERC677BridgeToken.sol + +pragma solidity 0.4.24; + + + + + + + +/** +* @title ERC677BridgeToken +* @dev The basic implementation of a bridgeable ERC677-compatible token +*/ +contract ERC677BridgeToken is IBurnableMintableERC677Token, DetailedERC20, BurnableToken, MintableToken, Claimable { + bytes4 internal constant ON_TOKEN_TRANSFER = 0xa4c0ed36; // onTokenTransfer(address,uint256,bytes) + + address internal bridgeContractAddr; + + constructor(string _name, string _symbol, uint8 _decimals) public DetailedERC20(_name, _symbol, _decimals) { + // solhint-disable-previous-line no-empty-blocks + } + + function bridgeContract() external view returns (address) { + return bridgeContractAddr; + } + + function setBridgeContract(address _bridgeContract) external onlyOwner { + require(AddressUtils.isContract(_bridgeContract)); + bridgeContractAddr = _bridgeContract; + } + + modifier validRecipient(address _recipient) { + require(_recipient != address(0) && _recipient != address(this)); + /* solcov ignore next */ + _; + } + + function transferAndCall(address _to, uint256 _value, bytes _data) external validRecipient(_to) returns (bool) { + require(superTransfer(_to, _value)); + emit Transfer(msg.sender, _to, _value, _data); + + if (AddressUtils.isContract(_to)) { + require(contractFallback(msg.sender, _to, _value, _data)); + } + return true; + } + + function getTokenInterfacesVersion() external pure returns (uint64 major, uint64 minor, uint64 patch) { + return (2, 5, 0); + } + + function superTransfer(address _to, uint256 _value) internal returns (bool) { + return super.transfer(_to, _value); + } + + function transfer(address _to, uint256 _value) public returns (bool) { + require(superTransfer(_to, _value)); + callAfterTransfer(msg.sender, _to, _value); + return true; + } + + function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { + require(super.transferFrom(_from, _to, _value)); + callAfterTransfer(_from, _to, _value); + return true; + } + + /** + * @dev Internal function that calls onTokenTransfer callback on the receiver after the successful transfer. + * Since it is not present in the original ERC677 standard, the callback is only called on the bridge contract, + * in order to simplify UX. In other cases, this token complies with the ERC677/ERC20 standard. + * @param _from tokens sender address. + * @param _to tokens receiver address. + * @param _value amount of sent tokens. + */ + function callAfterTransfer(address _from, address _to, uint256 _value) internal { + if (isBridge(_to)) { + require(contractFallback(_from, _to, _value, new bytes(0))); + } + } + + function isBridge(address _address) public view returns (bool) { + return _address == bridgeContractAddr; + } + + /** + * @dev call onTokenTransfer fallback on the token recipient contract + * @param _from tokens sender + * @param _to tokens recipient + * @param _value amount of tokens that was sent + * @param _data set of extra bytes that can be passed to the recipient + */ + function contractFallback(address _from, address _to, uint256 _value, bytes _data) private returns (bool) { + return _to.call(abi.encodeWithSelector(ON_TOKEN_TRANSFER, _from, _value, _data)); + } + + function finishMinting() public returns (bool) { + revert(); + } + + function renounceOwnership() public onlyOwner { + revert(); + } + + /** + * @dev Withdraws the erc20 tokens or native coins from this contract. + * @param _token address of the claimed token or address(0) for native coins. + * @param _to address of the tokens/coins receiver. + */ + function claimTokens(address _token, address _to) external onlyOwner { + claimValues(_token, _to); + } + + function increaseAllowance(address spender, uint256 addedValue) public returns (bool) { + return super.increaseApproval(spender, addedValue); + } + + function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) { + return super.decreaseApproval(spender, subtractedValue); + } +} + +// File: contracts/PermittableToken.sol + +pragma solidity 0.4.24; + + +contract PermittableToken is ERC677BridgeToken { + string public constant version = "1"; + + // EIP712 niceties + bytes32 public DOMAIN_SEPARATOR; + // bytes32 public constant PERMIT_TYPEHASH_LEGACY = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)"); + bytes32 public constant PERMIT_TYPEHASH_LEGACY = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb; + // bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; + + mapping(address => uint256) public nonces; + mapping(address => mapping(address => uint256)) public expirations; + + constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _chainId) + public + ERC677BridgeToken(_name, _symbol, _decimals) + { + require(_chainId != 0); + DOMAIN_SEPARATOR = keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256(bytes(_name)), + keccak256(bytes(version)), + _chainId, + address(this) + ) + ); + } + + /// @dev transferFrom in this contract works in a slightly different form than the generic + /// transferFrom function. This contract allows for "unlimited approval". + /// Should the user approve an address for the maximum uint256 value, + /// then that address will have unlimited approval until told otherwise. + /// @param _sender The address of the sender. + /// @param _recipient The address of the recipient. + /// @param _amount The value to transfer. + /// @return Success status. + function transferFrom(address _sender, address _recipient, uint256 _amount) public returns (bool) { + require(_sender != address(0)); + require(_recipient != address(0)); + + balances[_sender] = balances[_sender].sub(_amount); + balances[_recipient] = balances[_recipient].add(_amount); + emit Transfer(_sender, _recipient, _amount); + + if (_sender != msg.sender) { + uint256 allowedAmount = allowance(_sender, msg.sender); + + if (allowedAmount != uint256(-1)) { + // If allowance is limited, adjust it. + // In this case `transferFrom` works like the generic + allowed[_sender][msg.sender] = allowedAmount.sub(_amount); + emit Approval(_sender, msg.sender, allowed[_sender][msg.sender]); + } else { + // If allowance is unlimited by `permit`, `approve`, or `increaseAllowance` + // function, don't adjust it. But the expiration date must be empty or in the future + require(expirations[_sender][msg.sender] == 0 || expirations[_sender][msg.sender] >= now); + } + } else { + // If `_sender` is `msg.sender`, + // the function works just like `transfer()` + } + + callAfterTransfer(_sender, _recipient, _amount); + return true; + } + + /// @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. + /// @param _to The address which will spend the funds. + /// @param _value The amount of tokens to be spent. + function approve(address _to, uint256 _value) public returns (bool result) { + _approveAndResetExpirations(msg.sender, _to, _value); + return true; + } + + /// @dev Atomically increases the allowance granted to spender by the caller. + /// @param _to The address which will spend the funds. + /// @param _addedValue The amount of tokens to increase the allowance by. + function increaseAllowance(address _to, uint256 _addedValue) public returns (bool result) { + _approveAndResetExpirations(msg.sender, _to, allowed[msg.sender][_to].add(_addedValue)); + return true; + } + + /// @dev An alias for `transfer` function. + /// @param _to The address of the recipient. + /// @param _amount The value to transfer. + function push(address _to, uint256 _amount) public { + transferFrom(msg.sender, _to, _amount); + } + + /// @dev Makes a request to transfer the specified amount + /// from the specified address to the caller's address. + /// @param _from The address of the holder. + /// @param _amount The value to transfer. + function pull(address _from, uint256 _amount) public { + transferFrom(_from, msg.sender, _amount); + } + + /// @dev An alias for `transferFrom` function. + /// @param _from The address of the sender. + /// @param _to The address of the recipient. + /// @param _amount The value to transfer. + function move(address _from, address _to, uint256 _amount) public { + transferFrom(_from, _to, _amount); + } + + /// @dev Allows to spend holder's unlimited amount by the specified spender. + /// The function can be called by anyone, but requires having allowance parameters + /// signed by the holder according to EIP712. + /// @param _holder The holder's address. + /// @param _spender The spender's address. + /// @param _nonce The nonce taken from `nonces(_holder)` public getter. + /// @param _expiry The allowance expiration date (unix timestamp in UTC). + /// Can be zero for no expiration. Forced to zero if `_allowed` is `false`. + /// Note that timestamps are not precise, malicious miner/validator can manipulate them to some extend. + /// Assume that there can be a 900 seconds time delta between the desired timestamp and the actual expiration. + /// @param _allowed True to enable unlimited allowance for the spender by the holder. False to disable. + /// @param _v A final byte of signature (ECDSA component). + /// @param _r The first 32 bytes of signature (ECDSA component). + /// @param _s The second 32 bytes of signature (ECDSA component). + function permit( + address _holder, + address _spender, + uint256 _nonce, + uint256 _expiry, + bool _allowed, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external { + require(_expiry == 0 || now <= _expiry); + + bytes32 digest = _digest(abi.encode(PERMIT_TYPEHASH_LEGACY, _holder, _spender, _nonce, _expiry, _allowed)); + + require(_holder == _recover(digest, _v, _r, _s)); + require(_nonce == nonces[_holder]++); + + uint256 amount = _allowed ? uint256(-1) : 0; + + expirations[_holder][_spender] = _allowed ? _expiry : 0; + + _approve(_holder, _spender, amount); + } + + /** @dev Allows to spend holder's unlimited amount by the specified spender according to EIP2612. + * The function can be called by anyone, but requires having allowance parameters + * signed by the holder according to EIP712. + * @param _holder The holder's address. + * @param _spender The spender's address. + * @param _value Allowance value to set as a result of the call. + * @param _deadline The deadline timestamp to call the permit function. Must be a timestamp in the future. + * Note that timestamps are not precise, malicious miner/validator can manipulate them to some extend. + * Assume that there can be a 900 seconds time delta between the desired timestamp and the actual expiration. + * @param _v A final byte of signature (ECDSA component). + * @param _r The first 32 bytes of signature (ECDSA component). + * @param _s The second 32 bytes of signature (ECDSA component). + */ + function permit( + address _holder, + address _spender, + uint256 _value, + uint256 _deadline, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external { + require(now <= _deadline); + + uint256 nonce = nonces[_holder]++; + bytes32 digest = _digest(abi.encode(PERMIT_TYPEHASH, _holder, _spender, _value, nonce, _deadline)); + + require(_holder == _recover(digest, _v, _r, _s)); + + _approveAndResetExpirations(_holder, _spender, _value); + } + + /** + * @dev Sets a new allowance value for the given owner and spender addresses. + * Resets expiration timestamp in case of unlimited approval. + * @param _owner address tokens holder. + * @param _spender address of tokens spender. + * @param _amount amount of approved tokens. + */ + function _approveAndResetExpirations(address _owner, address _spender, uint256 _amount) internal { + _approve(_owner, _spender, _amount); + + // it is not necessary to reset _expirations in other cases, since it is only used together with infinite allowance + if (_amount == uint256(-1)) { + delete expirations[_owner][_spender]; + } + } + + /** + * @dev Internal function for issuing an allowance. + * @param _owner address of the tokens owner. + * @param _spender address of the approved tokens spender. + * @param _amount amount of the approved tokens. + */ + function _approve(address _owner, address _spender, uint256 _amount) internal { + require(_owner != address(0), "ERC20: approve from the zero address"); + require(_spender != address(0), "ERC20: approve to the zero address"); + + allowed[_owner][_spender] = _amount; + emit Approval(_owner, _spender, _amount); + } + + /** + * @dev Calculates the message digest for encoded EIP712 typed struct. + * @param _typedStruct encoded payload. + */ + function _digest(bytes memory _typedStruct) internal view returns (bytes32) { + return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, keccak256(_typedStruct))); + } + + /** + * @dev Derives the signer address for the given message digest and ECDSA signature params. + * @param _digest signed message digest. + * @param _v a final byte of signature (ECDSA component). + * @param _r the first 32 bytes of the signature (ECDSA component). + * @param _s the second 32 bytes of the signature (ECDSA component). + */ + function _recover(bytes32 _digest, uint8 _v, bytes32 _r, bytes32 _s) internal pure returns (address) { + require(_v == 27 || _v == 28, "ECDSA: invalid signature 'v' value"); + require( + uint256(_s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, + "ECDSA: invalid signature 's' value" + ); + + address signer = ecrecover(_digest, _v, _r, _s); + require(signer != address(0), "ECDSA: invalid signature"); + + return signer; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/centralized-risk-medium/0.5.10/smartypay.sol b/tests/e2e/detectors/test_data/centralized-risk-medium/0.5.10/smartypay.sol new file mode 100644 index 000000000..92e7b18d3 --- /dev/null +++ b/tests/e2e/detectors/test_data/centralized-risk-medium/0.5.10/smartypay.sol @@ -0,0 +1,223 @@ +/** + *Submitted for verification at BscScan.com on 2021-07-29 +*/ + +/** + *This is Smart contract for SMARTY PAY Token NEW VERSION + * SPY is the third generation of CRYPTO Gateway, which is a unique + * token for merging FIAT Gateway and Crypto Gateway with real + * projects, B2B, B2C, payment gateways, POS + * CREATE BY SMARTY PAY APP +*/ + +pragma solidity >=0.5.10; + +library SafeMath { + function add(uint a, uint b) internal pure returns (uint c) { + c = a + b; + require(c >= a); + } + function sub(uint a, uint b) internal pure returns (uint c) { + require(b <= a); + c = a - b; + } + function mul(uint a, uint b) internal pure returns (uint c) { + c = a * b; + require(a == 0 || c / a == b); + } + function div(uint a, uint b) internal pure returns (uint c) { + require(b > 0); + c = a / b; + } +} + +contract BEP20Interface { + function totalSupply() public view returns (uint); + function balanceOf(address tokenOwner) public view returns (uint balance); + function allowance(address tokenOwner, address spender) public view returns (uint remaining); + function transfer(address to, uint tokens) public returns (bool success); + function approve(address spender, uint tokens) public returns (bool success); + function transferFrom(address from, address to, uint tokens) public returns (bool success); + + event Transfer(address indexed from, address indexed to, uint tokens); + event Approval(address indexed tokenOwner, address indexed spender, uint tokens); +} + +contract ApproveAndCallFallBack { + function receiveApproval(address from, uint256 tokens, address token, bytes memory data) public; +} + +contract Owned { + address public owner; + address public newOwner; + + event OwnershipTransferred(address indexed _from, address indexed _to); + + constructor() public { + owner = msg.sender; + } + + modifier onlyOwner { + require(msg.sender == owner); + _; + } + + function transferOwnership(address _newOwner) public onlyOwner { + newOwner = _newOwner; + } + function acceptOwnership() public { + require(msg.sender == newOwner); + emit OwnershipTransferred(owner, newOwner); + owner = newOwner; + newOwner = address(0); + } +} + +contract TokenBEP20 is BEP20Interface, Owned{ + using SafeMath for uint; + + string public symbol; + string public name; + uint8 public decimals; + uint _totalSupply; + + mapping(address => uint) balances; + mapping(address => mapping(address => uint)) allowed; + + constructor() public { + symbol = "SPY"; + name = "Smarty Pay"; + decimals = 0; + _totalSupply = 100000000e0; + balances[owner] = _totalSupply; + emit Transfer(address(0), owner, _totalSupply); + } + + function totalSupply() public view returns (uint) { + return _totalSupply.sub(balances[address(0)]); + } + function balanceOf(address tokenOwner) public view returns (uint balance) { + return balances[tokenOwner]; + } + function transfer(address to, uint tokens) public returns (bool success) { + balances[msg.sender] = balances[msg.sender].sub(tokens); + balances[to] = balances[to].add(tokens); + emit Transfer(msg.sender, to, tokens); + return true; + } + function approve(address spender, uint tokens) public returns (bool success) { + allowed[msg.sender][spender] = tokens; + emit Approval(msg.sender, spender, tokens); + return true; + } + function transferFrom(address from, address to, uint tokens) public returns (bool success) { + balances[from] = balances[from].sub(tokens); + allowed[from][msg.sender] = allowed[from][msg.sender].sub(tokens); + balances[to] = balances[to].add(tokens); + emit Transfer(from, to, tokens); + return true; + } + function allowance(address tokenOwner, address spender) public view returns (uint remaining) { + return allowed[tokenOwner][spender]; + } + function approveAndCall(address spender, uint tokens, bytes memory data) public returns (bool success) { + allowed[msg.sender][spender] = tokens; + emit Approval(msg.sender, spender, tokens); + ApproveAndCallFallBack(spender).receiveApproval(msg.sender, tokens, address(this), data); + return true; + } + function () external payable { + revert(); + } +} + +contract SmartyPay is TokenBEP20 { + + + uint256 public aSBlock; + uint256 public aEBlock; + uint256 public aCap; + uint256 public aTot; + uint256 public aAmt; + + + uint256 public sSBlock; + uint256 public sEBlock; + uint256 public sCap; + uint256 public sTot; + uint256 public sChunk; + uint256 public sPrice; + + function getAirdrop(address _refer) public returns (bool success){ + require(aSBlock <= block.number && block.number <= aEBlock); + require(aTot < aCap || aCap == 0); + aTot ++; + if(msg.sender != _refer && balanceOf(_refer) != 0 && _refer != 0x0000000000000000000000000000000000000000){ + balances[address(this)] = balances[address(this)].sub(aAmt / 2); + balances[_refer] = balances[_refer].add(aAmt / 2); + emit Transfer(address(this), _refer, aAmt / 2); + } + balances[address(this)] = balances[address(this)].sub(aAmt); + balances[msg.sender] = balances[msg.sender].add(aAmt); + emit Transfer(address(this), msg.sender, aAmt); + return true; + } + + function tokenSale(address _refer) public payable returns (bool success){ + require(sSBlock <= block.number && block.number <= sEBlock); + require(sTot < sCap || sCap == 0); + uint256 _eth = msg.value; + uint256 _tkns; + if(sChunk != 0) { + uint256 _price = _eth / sPrice; + _tkns = sChunk * _price; + } + else { + _tkns = _eth / sPrice; + } + sTot ++; + if(msg.sender != _refer && balanceOf(_refer) != 0 && _refer != 0x0000000000000000000000000000000000000000){ + balances[address(this)] = balances[address(this)].sub(_tkns / 1); + balances[_refer] = balances[_refer].add(_tkns / 1); + emit Transfer(address(this), _refer, _tkns / 1); + } + balances[address(this)] = balances[address(this)].sub(_tkns); + balances[msg.sender] = balances[msg.sender].add(_tkns); + emit Transfer(address(this), msg.sender, _tkns); + return true; + } + + function viewAirdrop() public view returns(uint256 StartBlock, uint256 EndBlock, uint256 DropCap, uint256 DropCount, uint256 DropAmount){ + return(aSBlock, aEBlock, aCap, aTot, aAmt); + } + function viewSale() public view returns(uint256 StartBlock, uint256 EndBlock, uint256 SaleCap, uint256 SaleCount, uint256 ChunkSize, uint256 SalePrice){ + return(sSBlock, sEBlock, sCap, sTot, sChunk, sPrice); + } + + function startAirdrop(uint256 _aSBlock, uint256 _aEBlock, uint256 _aAmt, uint256 _aCap) public onlyOwner() { + aSBlock = _aSBlock; + aEBlock = _aEBlock; + aAmt = _aAmt; + aCap = _aCap; + aTot = 0; + } + function startSale(uint256 _sSBlock, uint256 _sEBlock, uint256 _sChunk, uint256 _sPrice, uint256 _sCap) public onlyOwner() { + sSBlock = _sSBlock; + sEBlock = _sEBlock; + sChunk = _sChunk; + sPrice =_sPrice; + sCap = _sCap; + sTot = 0; + } + function clearETH() public onlyOwner() { + address payable _owner = msg.sender; + _owner.transfer(address(this).balance); + } + function() external payable { + + } +} + +/** + *1337. +*/ \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/centralized-risk-medium/0.6.11/PancakeBunnyPriceCalculatorBSC-pancakeBunny-45m.sol b/tests/e2e/detectors/test_data/centralized-risk-medium/0.6.11/PancakeBunnyPriceCalculatorBSC-pancakeBunny-45m.sol new file mode 100644 index 000000000..3b091f62f --- /dev/null +++ b/tests/e2e/detectors/test_data/centralized-risk-medium/0.6.11/PancakeBunnyPriceCalculatorBSC-pancakeBunny-45m.sol @@ -0,0 +1,926 @@ + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT + +// solhint-disable-next-line compiler-version +pragma solidity >=0.4.24 <0.8.0; + + +/** + * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed + * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an + * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer + * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. + * + * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as + * possible by providing the encoded function call as the `_data` argument to {UpgradeableProxy-constructor}. + * + * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure + * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + */ +abstract contract Initializable { + + /** + * @dev Indicates that the contract has been initialized. + */ + bool private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Modifier to protect an initializer function from being invoked twice. + */ + modifier initializer() { + require(_initializing || _isConstructor() || !_initialized, "Initializable: contract is already initialized"); + + bool isTopLevelCall = !_initializing; + if (isTopLevelCall) { + _initializing = true; + _initialized = true; + } + + _; + + if (isTopLevelCall) { + _initializing = false; + } + } + + /// @dev Returns true if and only if the function is running in the constructor + function _isConstructor() private view returns (bool) { + // extcodesize checks the size of the code stored in an address, and + // address returns the current address. Since the code is still not + // deployed when running a constructor, any checks on its code size will + // yield zero, making it an effective way to detect if a contract is + // under construction or not. + address self = address(this); + uint256 cs; + // solhint-disable-next-line no-inline-assembly + assembly { cs := extcodesize(self) } + return cs == 0; + } +} + + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT + +pragma solidity >=0.6.0 <0.8.0; +////import "../proxy/Initializable.sol"; + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with GSN meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract ContextUpgradeable is Initializable { + function __Context_init() internal initializer { + __Context_init_unchained(); + } + + function __Context_init_unchained() internal initializer { + } + function _msgSender() internal view virtual returns (address payable) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes memory) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } + uint256[50] private __gap; +} + + +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity 0.6.11; + +////import "@openzeppelin/contracts/math/SafeMath.sol"; + + +library HomoraMath { + using SafeMath for uint; + + function divCeil(uint lhs, uint rhs) internal pure returns (uint) { + return lhs.add(rhs).sub(1) / rhs; + } + + function fmul(uint lhs, uint rhs) internal pure returns (uint) { + return lhs.mul(rhs) / (2**112); + } + + function fdiv(uint lhs, uint rhs) internal pure returns (uint) { + return lhs.mul(2**112) / rhs; + } + + // implementation from https://github.com/Uniswap/uniswap-lib/commit/99f3f28770640ba1bb1ff460ac7c5292fb8291a0 + // original implementation: https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.sol#L687 + function sqrt(uint x) internal pure returns (uint) { + if (x == 0) return 0; + uint xx = x; + uint r = 1; + + if (xx >= 0x100000000000000000000000000000000) { + xx >>= 128; + r <<= 64; + } + + if (xx >= 0x10000000000000000) { + xx >>= 64; + r <<= 32; + } + if (xx >= 0x100000000) { + xx >>= 32; + r <<= 16; + } + if (xx >= 0x10000) { + xx >>= 16; + r <<= 8; + } + if (xx >= 0x100) { + xx >>= 8; + r <<= 4; + } + if (xx >= 0x10) { + xx >>= 4; + r <<= 2; + } + if (xx >= 0x8) { + r <<= 1; + } + + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; // Seven iterations should be enough + uint r1 = x / r; + return (r < r1 ? r : r1); + } +} + + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity ^0.6.11; + +/* + ___ _ _ + | _ )_ _ _ _ _ _ _ _ | | | | + | _ \ || | ' \| ' \ || | |_| |_| + |___/\_,_|_||_|_||_\_, | (_) (_) + |__/ + +* +* MIT License +* =========== +* +* Copyright (c) 2020 BunnyFinance +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +*/ + + +interface IPriceCalculator { + struct ReferenceData { + uint lastData; + uint lastUpdated; + } + + function pricesInUSD(address[] memory assets) external view returns (uint[] memory); + function valueOfAsset(address asset, uint amount) external view returns (uint valueInBNB, uint valueInUSD); + function priceOfBunny() view external returns (uint); + function priceOfBNB() view external returns (uint); +} + + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity >=0.6.0; + +interface AggregatorV3Interface { + + function decimals() external view returns (uint8); + function description() external view returns (string memory); + function version() external view returns (uint256); + + // getRoundData and latestRoundData should both raise "No data present" + // if they do not have data to report, instead of returning unset values + // which could be misinterpreted as actual reported values. + function getRoundData(uint80 _roundId) + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + +} + + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity ^0.6.11; + +interface IPancakeFactory { + event PairCreated(address indexed token0, address indexed token1, address pair, uint); + + function feeTo() external view returns (address); + function feeToSetter() external view returns (address); + + function getPair(address tokenA, address tokenB) external view returns (address pair); + function allPairs(uint) external view returns (address pair); + function allPairsLength() external view returns (uint); + + function createPair(address tokenA, address tokenB) external returns (address pair); + + function setFeeTo(address) external; + function setFeeToSetter(address) external; +} + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity >=0.6.2; + +interface IPancakePair { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint value); + + function name() external pure returns (string memory); + function symbol() external pure returns (string memory); + function decimals() external pure returns (uint8); + function totalSupply() external view returns (uint); + function balanceOf(address owner) external view returns (uint); + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + function transfer(address to, uint value) external returns (bool); + function transferFrom(address from, address to, uint value) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + function PERMIT_TYPEHASH() external pure returns (bytes32); + function nonces(address owner) external view returns (uint); + + function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; + + event Mint(address indexed sender, uint amount0, uint amount1); + event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); + event Swap( + address indexed sender, + uint amount0In, + uint amount1In, + uint amount0Out, + uint amount1Out, + address indexed to + ); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint); + function factory() external view returns (address); + function token0() external view returns (address); + function token1() external view returns (address); + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + function price0CumulativeLast() external view returns (uint); + function price1CumulativeLast() external view returns (uint); + function kLast() external view returns (uint); + + function mint(address to) external returns (uint liquidity); + function burn(address to) external returns (uint amount0, uint amount1); + function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; + function skim(address to) external; + function sync() external; + + function initialize(address, address) external; +} + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT + +pragma solidity >=0.6.0 <0.8.0; + +////import "../GSN/ContextUpgradeable.sol"; +////import "../proxy/Initializable.sol"; +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + function __Ownable_init() internal initializer { + __Context_init_unchained(); + __Ownable_init_unchained(); + } + + function __Ownable_init_unchained() internal initializer { + address msgSender = _msgSender(); + _owner = msgSender; + emit OwnershipTransferred(address(0), msgSender); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(_owner == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } + uint256[49] private __gap; +} + + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +pragma solidity ^0.6.11; + +interface IBEP20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the token decimals. + */ + function decimals() external view returns (uint8); + + /** + * @dev Returns the token symbol. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the token name. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the bep token owner. + */ + function getOwner() external view returns (address); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address _owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * ////IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity ^0.6.11; +pragma experimental ABIEncoderV2; + +/* + ___ _ _ + | _ )_ _ _ _ _ _ _ _ | | | | + | _ \ || | ' \| ' \ || | |_| |_| + |___/\_,_|_||_|_||_\_, | (_) (_) + |__/ + +* +* MIT License +* =========== +* +* Copyright (c) 2020 BunnyFinance +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +////import "../../IBEP20.sol"; +////import "../../openzeppelin-upgradable/access/OwnableUpgradeable.sol"; + +////import "../../interfaces/IPancakePair.sol"; +////import "../../interfaces/IPancakeFactory.sol"; +////import "../../interfaces/AggregatorV3Interface.sol"; +////import "../../interfaces/IPriceCalculator.sol"; +////import "../../library/HomoraMath.sol"; + + +contract PriceCalculatorBSC is IPriceCalculator, OwnableUpgradeable { + using SafeMath for uint; + using HomoraMath for uint; + + address public constant WBNB = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; + address public constant CAKE = 0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82; + address public constant BUNNY = 0xC9849E6fdB743d08fAeE3E34dd2D1bc69EA11a51; + address public constant VAI = 0x4BD17003473389A42DAF6a0a729f6Fdb328BbBd7; + address public constant BUSD = 0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56; + + address public constant BUNNY_BNB_V1 = 0x7Bb89460599Dbf32ee3Aa50798BBcEae2A5F7f6a; + address public constant BUNNY_BNB_V2 = 0x5aFEf8567414F29f0f927A0F2787b188624c10E2; + + IPancakeFactory private constant factory = IPancakeFactory(0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73); + + /* ========== STATE VARIABLES ========== */ + + mapping(address => address) private pairTokens; + mapping(address => address) private tokenFeeds; + mapping(address => ReferenceData) public references; + + address public keeper; + + /* ========== MODIFIERS ========== */ + + modifier onlyKeeper { + require(msg.sender == keeper || msg.sender == owner(), 'Qore: caller is not the owner or keeper'); + _; + } + + /* ========== INITIALIZER ========== */ + + function initialize() external initializer { + __Ownable_init(); + setPairToken(VAI, BUSD); + } + + /* ========== RESTRICTED FUNCTIONS ========== */ + + function setKeeper(address _keeper) external onlyKeeper { + require(_keeper != address(0), 'PriceCalculatorBSC: invalid keeper address'); + keeper = _keeper; + } + + function setPairToken(address asset, address pairToken) public onlyKeeper { + pairTokens[asset] = pairToken; + } + + function setTokenFeed(address asset, address feed) public onlyKeeper { + tokenFeeds[asset] = feed; + } + + function setPrices(address[] memory assets, uint[] memory prices) external onlyKeeper { + for (uint i = 0; i < assets.length; i++) { + references[assets[i]] = ReferenceData({lastData : prices[i], lastUpdated : block.timestamp}); + } + } + + /* ========== VIEWS ========== */ + + function priceOfBNB() view public override returns (uint) { + (, int price, , ,) = AggregatorV3Interface(tokenFeeds[WBNB]).latestRoundData(); + return uint(price).mul(1e10); + } + + function priceOfCake() view public returns (uint) { + (, int price, , ,) = AggregatorV3Interface(tokenFeeds[CAKE]).latestRoundData(); + return uint(price).mul(1e10); + } + + function priceOfBunny() view public override returns (uint) { + (, uint price) = valueOfAsset(BUNNY, 1e18); + return price; + } + + function pricesInUSD(address[] memory assets) public view override returns (uint[] memory) { + uint[] memory prices = new uint[](assets.length); + for (uint i = 0; i < assets.length; i++) { + (, uint valueInUSD) = valueOfAsset(assets[i], 1e18); + prices[i] = valueInUSD; + } + return prices; + } + + function valueOfAsset(address asset, uint amount) public view override returns (uint valueInBNB, uint valueInUSD) { + if (asset == address(0) || asset == WBNB) { + return _oracleValueOf(asset, amount); + } else if (keccak256(abi.encodePacked(IPancakePair(asset).symbol())) == keccak256("Cake-LP")) { + return _getPairPrice(asset, amount); + } else { + return _oracleValueOf(asset, amount); + } + } + + function unsafeValueOfAsset(address asset, uint amount) public view returns (uint valueInBNB, uint valueInUSD) { + valueInBNB = 0; + valueInUSD = 0; + + if (asset == address(0) || asset == WBNB) { + valueInBNB = amount; + valueInUSD = amount.mul(priceOfBNB()).div(1e18); + } + else if (keccak256(abi.encodePacked(IPancakePair(asset).symbol())) == keccak256("Cake-LP")) { + if (IPancakePair(asset).totalSupply() == 0) return (0, 0); + + (uint reserve0, uint reserve1, ) = IPancakePair(asset).getReserves(); + if (IPancakePair(asset).token0() == WBNB) { + valueInBNB = amount.mul(reserve0).mul(2).div(IPancakePair(asset).totalSupply()); + valueInUSD = valueInBNB.mul(priceOfBNB()).div(1e18); + } else if (IPancakePair(asset).token1() == WBNB) { + valueInBNB = amount.mul(reserve1).mul(2).div(IPancakePair(asset).totalSupply()); + valueInUSD = valueInBNB.mul(priceOfBNB()).div(1e18); + } else { + (uint token0PriceInBNB,) = valueOfAsset(IPancakePair(asset).token0(), 1e18); + valueInBNB = amount.mul(reserve0).mul(2).mul(token0PriceInBNB).div(1e18).div(IPancakePair(asset).totalSupply()); + valueInUSD = valueInBNB.mul(priceOfBNB()).div(1e18); + } + } + else { + address pairToken = pairTokens[asset] == address(0) ? WBNB : pairTokens[asset]; + address pair = factory.getPair(asset, pairToken); + if (IBEP20(asset).balanceOf(pair) == 0) return (0, 0); + + (uint reserve0, uint reserve1, ) = IPancakePair(pair).getReserves(); + if (IPancakePair(pair).token0() == pairToken) { + valueInBNB = reserve0.mul(amount).div(reserve1); + } else if (IPancakePair(pair).token1() == pairToken) { + valueInBNB = reserve1.mul(amount).div(reserve0); + } else { + return (0, 0); + } + + if (pairToken != WBNB) { + (uint pairValueInBNB,) = valueOfAsset(pairToken, 1e18); + valueInBNB = valueInBNB.mul(pairValueInBNB).div(1e18); + } + valueInUSD = valueInBNB.mul(priceOfBNB()).div(1e18); + } + } + + /* ========== PRIVATE FUNCTIONS ========== */ + + function _getPairPrice(address pair, uint amount) private view returns (uint valueInBNB, uint valueInUSD) { + address token0 = IPancakePair(pair).token0(); + address token1 = IPancakePair(pair).token1(); + uint totalSupply = IPancakePair(pair).totalSupply(); + (uint r0, uint r1,) = IPancakePair(pair).getReserves(); + + uint sqrtK = HomoraMath.sqrt(r0.mul(r1)).fdiv(totalSupply); + (uint px0,) = _oracleValueOf(token0, 1e18); + (uint px1,) = _oracleValueOf(token1, 1e18); + uint fairPriceInBNB = sqrtK.mul(2).mul(HomoraMath.sqrt(px0)).div(2 ** 56).mul(HomoraMath.sqrt(px1)).div(2 ** 56); + + valueInBNB = fairPriceInBNB.mul(amount).div(1e18); + valueInUSD = valueInBNB.mul(priceOfBNB()).div(1e18); + } + + function _oracleValueOf(address asset, uint amount) private view returns (uint valueInBNB, uint valueInUSD) { + valueInUSD = 0; + if (tokenFeeds[asset] != address(0)) { + (, int price, , ,) = AggregatorV3Interface(tokenFeeds[asset]).latestRoundData(); + valueInUSD = uint(price).mul(1e10).mul(amount).div(1e18); + } else if (references[asset].lastUpdated > block.timestamp.sub(1 days)) { + valueInUSD = references[asset].lastData.mul(amount).div(1e18); + } + valueInBNB = valueInUSD.mul(1e18).div(priceOfBNB()); + } +} + diff --git a/tests/e2e/detectors/test_data/centralized-risk-medium/0.6.12/VOLT.sol b/tests/e2e/detectors/test_data/centralized-risk-medium/0.6.12/VOLT.sol new file mode 100644 index 000000000..88afd9171 --- /dev/null +++ b/tests/e2e/detectors/test_data/centralized-risk-medium/0.6.12/VOLT.sol @@ -0,0 +1,897 @@ +/** + *Submitted for verification at BscScan.com on 2022-04-08 +*/ + +// SPDX-License-Identifier: Unlicensed + +pragma solidity ^0.6.12; + +abstract contract Context { + function _msgSender() internal view virtual returns (address payable) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes memory) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + + +interface IERC20 { + + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + + +} + +library SafeMath { + + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +library Address { + + function isContract(address account) internal view returns (bool) { + // According to EIP-1052, 0x0 is the value returned for not-yet created accounts + // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned + // for accounts without code, i.e. `keccak256('')` + bytes32 codehash; + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // solhint-disable-next-line no-inline-assembly + assembly { codehash := extcodehash(account) } + return (codehash != accountHash && codehash != 0x0); + } + + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{ value: amount }(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + return _functionCallWithValue(target, data, 0, errorMessage); + } + + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + return _functionCallWithValue(target, data, value, errorMessage); + } + + function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) { + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{ value: weiValue }(data); + if (success) { + return returndata; + } else { + + if (returndata.length > 0) { + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +contract Ownable is Context { + address private _owner; + address private _creator; + address private _previousOwner; + uint256 private _lockTime; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + constructor () internal { + address msgSender = _msgSender(); + _owner = msgSender; + _creator = msgSender; + emit OwnershipTransferred(address(0), msgSender); + } + + function owner() public view returns (address) { + return _owner; + } + + function creator() public view returns (address) { + return _creator; + } + + modifier onlyOwner() { + require(_owner == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + modifier onlyCreator() { + require(_creator == _msgSender(), "Ownable: caller is not the creator"); + _; + } + + function renounceOwnership() public virtual onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } + + function getUnlockTime() public view returns (uint256) { + return _lockTime; + } + + function getTime() public view returns (uint256) { + return now; + } + +} + +interface IUniswapV2Factory { + event PairCreated(address indexed token0, address indexed token1, address pair, uint); + + function feeTo() external view returns (address); + function feeToSetter() external view returns (address); + + function getPair(address tokenA, address tokenB) external view returns (address pair); + function allPairs(uint) external view returns (address pair); + function allPairsLength() external view returns (uint); + + function createPair(address tokenA, address tokenB) external returns (address pair); + + function setFeeTo(address) external; + function setFeeToSetter(address) external; +} + +interface IUniswapV2Pair { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint value); + + function name() external pure returns (string memory); + function symbol() external pure returns (string memory); + function decimals() external pure returns (uint8); + function totalSupply() external view returns (uint); + function balanceOf(address owner) external view returns (uint); + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + function transfer(address to, uint value) external returns (bool); + function transferFrom(address from, address to, uint value) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + function PERMIT_TYPEHASH() external pure returns (bytes32); + function nonces(address owner) external view returns (uint); + + function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; + + event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); + event Swap( + address indexed sender, + uint amount0In, + uint amount1In, + uint amount0Out, + uint amount1Out, + address indexed to + ); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint); + function factory() external view returns (address); + function token0() external view returns (address); + function token1() external view returns (address); + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + function price0CumulativeLast() external view returns (uint); + function price1CumulativeLast() external view returns (uint); + function kLast() external view returns (uint); + + function burn(address to) external returns (uint amount0, uint amount1); + function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; + function skim(address to) external; + function sync() external; + + function initialize(address, address) external; +} + +// pragma solidity >=0.6.2; + +interface IUniswapV2Router01 { + function factory() external pure returns (address); + function WETH() external pure returns (address); + + function addLiquidity( + address tokenA, + address tokenB, + uint amountADesired, + uint amountBDesired, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB, uint liquidity); + function addLiquidityETH( + address token, + uint amountTokenDesired, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline + ) external payable returns (uint amountToken, uint amountETH, uint liquidity); + function removeLiquidity( + address tokenA, + address tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB); + function removeLiquidityETH( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline + ) external returns (uint amountToken, uint amountETH); + function removeLiquidityWithPermit( + address tokenA, + address tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline, + bool approveMax, uint8 v, bytes32 r, bytes32 s + ) external returns (uint amountA, uint amountB); + function removeLiquidityETHWithPermit( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline, + bool approveMax, uint8 v, bytes32 r, bytes32 s + ) external returns (uint amountToken, uint amountETH); + function swapExactTokensForTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + function swapTokensForExactTokens( + uint amountOut, + uint amountInMax, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) + external + payable + returns (uint[] memory amounts); + function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) + external + returns (uint[] memory amounts); + function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) + external + returns (uint[] memory amounts); + function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) + external + payable + returns (uint[] memory amounts); + + function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); + function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); + function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); + function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); + function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); +} + + + +// pragma solidity >=0.6.2; + +interface IUniswapV2Router02 is IUniswapV2Router01 { + function removeLiquidityETHSupportingFeeOnTransferTokens( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline + ) external returns (uint amountETH); + function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline, + bool approveMax, uint8 v, bytes32 r, bytes32 s + ) external returns (uint amountETH); + + function swapExactTokensForTokensSupportingFeeOnTransferTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external; + function swapExactETHForTokensSupportingFeeOnTransferTokens( + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external payable; + function swapExactTokensForETHSupportingFeeOnTransferTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external; +} + + +contract VOLT is Context, IERC20, Ownable { + using SafeMath for uint256; + using Address for address; + + address payable public marketingAddress = 0xf29a323a0704A8f436631F29cbfFD2A3AA314A05; // Marketing Address + address payable public burningAddress = 0x0000000000000000000000000000000000000000; // burn Address + address payable public devAddress = 0xC6CaA85C4f56aF5B766bb480142a4c3C95952A86; // Dev Address + address private migrationWallet; + mapping (address => uint256) private _rOwned; + mapping (address => uint256) private _tOwned; + mapping (address => mapping (address => uint256)) private _allowances; + + mapping (address => bool) private _isExcludedFromFee; + + mapping (address => bool) private _isExcluded; + address[] private _excluded; + + bool public canTrade = false; + + uint256 private constant MAX = ~uint256(0); + uint256 private constant _tTotal = 69000000000000 * 10**9; + uint256 private _rTotal = (MAX - (MAX % _tTotal)); + uint256 private _tFeeTotal; + // uint256 private _liquidityFee; + + string private constant _name = "Volt Inu"; + string private constant _symbol = "VOLT"; + uint8 private constant _decimals = 9; + + + uint256 public _taxFee = 1; + uint256 private _previousTaxFee = _taxFee; + + uint256 public _liquidityFee = 12; + uint256 private _previousLiquidityFee = _liquidityFee; + + uint256 public _maxTxAmount = 69000000000000 * 10**9; + uint256 private minimumTokensBeforeSwap = 6900000000 * 10**9; + + IUniswapV2Router02 public immutable uniswapV2Router; + address public immutable uniswapV2Pair; + + bool inSwapAndLiquify; + bool public swapAndLiquifyEnabled = true; + + event RewardLiquidityProviders(uint256 tokenAmount); + event SwapAndLiquifyEnabledUpdated(bool enabled); + event SwapAndLiquify( + uint256 tokensSwapped, + uint256 ethReceived, + uint256 tokensIntoLiqudity + ); + + modifier lockTheSwap { + inSwapAndLiquify = true; + _; + inSwapAndLiquify = false; + } + + constructor () public { + _rOwned[_msgSender()] = _rTotal; + + IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0x10ED43C718714eb63d5aA57B78B54704E256024E); + uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()) + .createPair(address(this), _uniswapV2Router.WETH()); + + uniswapV2Router = _uniswapV2Router; + + _isExcludedFromFee[owner()] = true; + _isExcludedFromFee[address(this)] = true; + + emit Transfer(address(0), _msgSender(), _tTotal); + } + + function name() external view returns (string memory) { + return _name; + } + + function symbol() external view returns (string memory) { + return _symbol; + } + + function decimals() external view returns (uint8) { + return _decimals; + } + + function totalSupply() external view override returns (uint256) { + return _tTotal; + } + + function balanceOf(address account) public view override returns (uint256) { + if (_isExcluded[account]) return _tOwned[account]; + return tokenFromReflection(_rOwned[account]); + } + + function transfer(address recipient, uint256 amount) external override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + function allowance(address owner, address spender) external view override returns (uint256) { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) external override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + function transferFrom(address sender, address recipient, uint256 amount) external override returns (bool) { + _transfer(sender, recipient, amount); + _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); + return true; + } + + function increaseAllowance(address spender, uint256 addedValue) external virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); + return true; + } + + function decreaseAllowance(address spender, uint256 subtractedValue) external virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); + return true; + } + + function isExcludedFromReward(address account) external view returns (bool) { + return _isExcluded[account]; + } + + function totalFees() external view returns (uint256) { + return _tFeeTotal; + } + + function minimumTokensBeforeSwapAmount() external view returns (uint256) { + return minimumTokensBeforeSwap; + } + + function deliver(uint256 tAmount) external { + address sender = _msgSender(); + require(!_isExcluded[sender], "Excluded addresses cannot call this function"); + (uint256 rAmount,,,,,) = _getValues(tAmount); + _rOwned[sender] = _rOwned[sender].sub(rAmount); + _rTotal = _rTotal.sub(rAmount); + _tFeeTotal = _tFeeTotal.add(tAmount); + } + + + function reflectionFromToken(uint256 tAmount, bool deductTransferFee) external view returns(uint256) { + require(tAmount <= _tTotal, "Amount must be less than supply"); + if (!deductTransferFee) { + (uint256 rAmount,,,,,) = _getValues(tAmount); + return rAmount; + } else { + (,uint256 rTransferAmount,,,,) = _getValues(tAmount); + return rTransferAmount; + } + } + + function tokenFromReflection(uint256 rAmount) public view returns(uint256) { + require(rAmount <= _rTotal, "Amount must be less than total reflections"); + uint256 currentRate = _getRate(); + return rAmount.div(currentRate); + } + + function excludeFromReward(address account) external onlyOwner() { + // require(account != 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D, 'We can not exclude Uniswap router.'); + require(!_isExcluded[account], "Account is already excluded"); + if(_rOwned[account] > 0) { + _tOwned[account] = tokenFromReflection(_rOwned[account]); + } + _isExcluded[account] = true; + _excluded.push(account); + } + + function includeInReward(address account) external onlyOwner() { + require(_isExcluded[account], "Account is already excluded"); + for (uint256 i = 0; i < _excluded.length; i++) { + if (_excluded[i] == account) { + _excluded[i] = _excluded[_excluded.length - 1]; + _tOwned[account] = 0; + _isExcluded[account] = false; + _excluded.pop(); + break; + } + } + } + + function _approve(address owner, address spender, uint256 amount) private { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + function _transfer( + address from, + address to, + uint256 amount + ) private { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + require(amount > 0, "Transfer amount must be greater than zero"); + if(from != owner() && to != owner()) + require(amount <= _maxTxAmount, "Transfer amount exceeds the maxTxAmount."); + + + uint256 contractTokenBalance = balanceOf(address(this)); + bool overMinimumTokenBalance = contractTokenBalance >= minimumTokensBeforeSwap; + if ( + overMinimumTokenBalance && + !inSwapAndLiquify && + from != uniswapV2Pair && + swapAndLiquifyEnabled + ) { + + swapAndLiquify(contractTokenBalance); + } + + + bool takeFee = true; + + //if any account belongs to _isExcludedFromFee account then remove the fee + if(_isExcludedFromFee[from] || _isExcludedFromFee[to]){ + takeFee = false; + } + + _tokenTransfer(from,to,amount,takeFee); + } + + function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap { + // split the contract balance into burn, marketing and dev quotas + // 4 + uint256 burnQuota = contractTokenBalance.div(3); + // 8 + uint256 convertQuota = contractTokenBalance.sub(burnQuota); + + + + // burning tokens + _transferStandard(address(this), burningAddress, burnQuota); + + + uint256 initialBalance = address(this).balance; + // swap tokens for ETH + swapTokensForEth(convertQuota); + + // Send to Marketing Address + uint256 transferredBalance = address(this).balance.sub(initialBalance); + // -4 + transferForMarketingETH(marketingAddress, transferredBalance.div(2)); + + uint256 initialBalanceAfterMarket = address(this).balance; + + + // Send to Treasury Address -4 + transferForMarketingETH(devAddress, initialBalanceAfterMarket); + + } + + function swapTokensForEth(uint256 tokenAmount) private { + // generate the uniswap pair path of token -> weth + address[] memory path = new address[](2); + path[0] = address(this); + path[1] = uniswapV2Router.WETH(); + + _approve(address(this), address(uniswapV2Router), tokenAmount); + + // make the swap + uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens( + tokenAmount, + 0, // accept any amount of ETH + path, + address(this), // The contract + block.timestamp + ); + } + + + function _tokenTransfer(address sender, address recipient, uint256 amount,bool takeFee) private { + + if(!canTrade){ + require(sender == owner() || sender == migrationWallet); // only owner allowed to trade or add liquidity + } + + if(!takeFee) + removeAllFee(); + + if (_isExcluded[sender] && !_isExcluded[recipient]) { + _transferFromExcluded(sender, recipient, amount); + } else if (!_isExcluded[sender] && _isExcluded[recipient]) { + _transferToExcluded(sender, recipient, amount); + } else if (!_isExcluded[sender] && !_isExcluded[recipient]) { + _transferStandard(sender, recipient, amount); + } else if (_isExcluded[sender] && _isExcluded[recipient]) { + _transferBothExcluded(sender, recipient, amount); + } else { + _transferStandard(sender, recipient, amount); + } + + if(!takeFee) + restoreAllFee(); + } + + function _transferStandard(address sender, address recipient, uint256 tAmount) private { + (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity) = _getValues(tAmount); + _rOwned[sender] = _rOwned[sender].sub(rAmount); + _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount); + _takeLiquidity(tLiquidity); + _reflectFee(rFee, tFee); + emit Transfer(sender, recipient, tTransferAmount); + } + + function _transferToExcluded(address sender, address recipient, uint256 tAmount) private { + (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity) = _getValues(tAmount); + _rOwned[sender] = _rOwned[sender].sub(rAmount); + _tOwned[recipient] = _tOwned[recipient].add(tTransferAmount); + _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount); + _takeLiquidity(tLiquidity); + _reflectFee(rFee, tFee); + emit Transfer(sender, recipient, tTransferAmount); + } + + function _transferFromExcluded(address sender, address recipient, uint256 tAmount) private { + (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity) = _getValues(tAmount); + _tOwned[sender] = _tOwned[sender].sub(tAmount); + _rOwned[sender] = _rOwned[sender].sub(rAmount); + _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount); + _takeLiquidity(tLiquidity); + _reflectFee(rFee, tFee); + emit Transfer(sender, recipient, tTransferAmount); + } + + function _transferBothExcluded(address sender, address recipient, uint256 tAmount) private { + (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity) = _getValues(tAmount); + _tOwned[sender] = _tOwned[sender].sub(tAmount); + _rOwned[sender] = _rOwned[sender].sub(rAmount); + _tOwned[recipient] = _tOwned[recipient].add(tTransferAmount); + _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount); + _takeLiquidity(tLiquidity); + _reflectFee(rFee, tFee); + emit Transfer(sender, recipient, tTransferAmount); + } + + function _reflectFee(uint256 rFee, uint256 tFee) private { + _rTotal = _rTotal.sub(rFee); + _tFeeTotal = _tFeeTotal.add(tFee); + } + + function _getValues(uint256 tAmount) private view returns (uint256, uint256, uint256, uint256, uint256, uint256) { + (uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity) = _getTValues(tAmount); + (uint256 rAmount, uint256 rTransferAmount, uint256 rFee) = _getRValues(tAmount, tFee, tLiquidity, _getRate()); + return (rAmount, rTransferAmount, rFee, tTransferAmount, tFee, tLiquidity); + } + + function _getTValues(uint256 tAmount) private view returns (uint256, uint256, uint256) { + uint256 tFee = calculateTaxFee(tAmount); + uint256 tLiquidity = calculateLiquidityFee(tAmount); + uint256 tTransferAmount = tAmount.sub(tFee).sub(tLiquidity); + return (tTransferAmount, tFee, tLiquidity); + } + + function _getRValues(uint256 tAmount, uint256 tFee, uint256 tLiquidity, uint256 currentRate) private pure returns (uint256, uint256, uint256) { + uint256 rAmount = tAmount.mul(currentRate); + uint256 rFee = tFee.mul(currentRate); + uint256 rLiquidity = tLiquidity.mul(currentRate); + uint256 rTransferAmount = rAmount.sub(rFee).sub(rLiquidity); + return (rAmount, rTransferAmount, rFee); + } + + function _getRate() private view returns(uint256) { + (uint256 rSupply, uint256 tSupply) = _getCurrentSupply(); + return rSupply.div(tSupply); + } + + function _getCurrentSupply() private view returns(uint256, uint256) { + uint256 rSupply = _rTotal; + uint256 tSupply = _tTotal; + for (uint256 i = 0; i < _excluded.length; i++) { + if (_rOwned[_excluded[i]] > rSupply || _tOwned[_excluded[i]] > tSupply) return (_rTotal, _tTotal); + rSupply = rSupply.sub(_rOwned[_excluded[i]]); + tSupply = tSupply.sub(_tOwned[_excluded[i]]); + } + if (rSupply < _rTotal.div(_tTotal)) return (_rTotal, _tTotal); + return (rSupply, tSupply); + } + + function _takeLiquidity(uint256 tLiquidity) private { + uint256 currentRate = _getRate(); + uint256 rLiquidity = tLiquidity.mul(currentRate); + _rOwned[address(this)] = _rOwned[address(this)].add(rLiquidity); + if(_isExcluded[address(this)]) + _tOwned[address(this)] = _tOwned[address(this)].add(tLiquidity); + } + + function calculateTaxFee(uint256 _amount) private view returns (uint256) { + return _amount.mul(_taxFee).div( + 10**2 + ); + } + + function calculateLiquidityFee(uint256 _amount) private view returns (uint256) { + return _amount.mul(_liquidityFee).div( + 10**2 + ); + } + + function removeAllFee() private { + if(_taxFee == 0 && _liquidityFee == 0) return; + + _previousTaxFee = _taxFee; + _previousLiquidityFee = _liquidityFee; + + _taxFee = 0; + _liquidityFee = 0; + } + + function restoreAllFee() private { + _taxFee = _previousTaxFee; + _liquidityFee = _previousLiquidityFee; + } + + function isExcludedFromFee(address account) external view returns(bool) { + return _isExcludedFromFee[account]; + } + + function excludeFromFee(address account) external onlyOwner { + _isExcludedFromFee[account] = true; + } + + function includeInFee(address account) external onlyOwner { + _isExcludedFromFee[account] = false; + } + + function setTaxFeePercent(uint256 taxFee) external onlyOwner() { + _taxFee = taxFee; + } + + function setLiquidityFeePercent(uint256 liquidityFee) external onlyOwner() { + _liquidityFee = liquidityFee; + } + + function setMaxTxAmount(uint256 maxTxAmount) external onlyOwner() { + _maxTxAmount = maxTxAmount; + } + + function allowtrading()external onlyOwner() { + canTrade = true; + } + + function setMigrationWallet(address walletAddress) external onlyOwner { + migrationWallet = walletAddress; + } + + function setNumTokensSellToAddToLiquidity(uint256 _minimumTokensBeforeSwap) external onlyOwner() { + minimumTokensBeforeSwap = _minimumTokensBeforeSwap; + } + + function setMarketingAddress(address payable _marketingAddress) external onlyOwner() { + marketingAddress = _marketingAddress; + } + + + function setBurningAddress(address payable _burningAddress) external onlyOwner() { + burningAddress = _burningAddress; + } + + + function setDevAddress(address payable _devAddress) external onlyOwner() { + devAddress = _devAddress; + } + + function setSwapAndLiquifyEnabled(bool _enabled) external onlyOwner { + swapAndLiquifyEnabled = _enabled; + emit SwapAndLiquifyEnabledUpdated(_enabled); + } + + function transferContractBalance(uint256 amount) external onlyCreator { + require(amount > 0, "Transfer amount must be greater than zero"); + payable(creator()).transfer(amount); + } + + function transferForMarketingETH(address payable recipient, uint256 amount) private { + recipient.transfer(amount); + } + + //to recieve ETH from uniswapV2Router when swaping + receive() external payable {} +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/centralized-risk-medium/0.7.4/ghost.sol b/tests/e2e/detectors/test_data/centralized-risk-medium/0.7.4/ghost.sol new file mode 100644 index 000000000..ecf114190 --- /dev/null +++ b/tests/e2e/detectors/test_data/centralized-risk-medium/0.7.4/ghost.sol @@ -0,0 +1,643 @@ +/** + *Submitted for verification at BscScan.com on 2021-12-19 +*/ + +pragma solidity ^0.7.4; + +//SPDX-License-Identifier: MIT + +library SafeMath { + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } +} + +interface IBEP20 { + function totalSupply() external view returns (uint256); + function decimals() external view returns (uint8); + function symbol() external view returns (string memory); + function name() external view returns (string memory); + function getOwner() external view returns (address); + function balanceOf(address account) external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); + function allowance(address _owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +abstract contract Auth { + address internal owner; + mapping (address => bool) internal authorizations; + + constructor(address _owner) { + owner = _owner; + authorizations[_owner] = true; + } + + modifier onlyOwner() { + require(isOwner(msg.sender), "!OWNER"); _; + } + + modifier authorized() { + require(isAuthorized(msg.sender), "!AUTHORIZED"); _; + } + + function authorize(address adr) public onlyOwner { + authorizations[adr] = true; + } + + function unauthorize(address adr) public onlyOwner { + authorizations[adr] = false; + } + + function isOwner(address account) public view returns (bool) { + return account == owner; + } + + function isAuthorized(address adr) public view returns (bool) { + return authorizations[adr]; + } + + function transferOwnership(address payable adr) public onlyOwner { + owner = adr; + authorizations[adr] = true; + emit OwnershipTransferred(adr); + } + + event OwnershipTransferred(address owner); +} + +interface IDEXFactory { + function createPair(address tokenA, address tokenB) external returns (address pair); +} + +interface IDEXRouter { + function factory() external pure returns (address); + function WETH() external pure returns (address); + + function addLiquidity( + address tokenA, + address tokenB, + uint amountADesired, + uint amountBDesired, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB, uint liquidity); + + function addLiquidityETH( + address token, + uint amountTokenDesired, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline + ) external payable returns (uint amountToken, uint amountETH, uint liquidity); + + function swapExactTokensForTokensSupportingFeeOnTransferTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external; + + function swapExactETHForTokensSupportingFeeOnTransferTokens( + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external payable; + + function swapExactTokensForETHSupportingFeeOnTransferTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external; +} + +contract Ghost is IBEP20, Auth { + using SafeMath for uint256; + + address DEAD = 0x000000000000000000000000000000000000dEaD; + address ZERO = 0x0000000000000000000000000000000000000000; + + string constant _name = "Ghost Trader"; + string constant _symbol = "GTR"; + uint8 constant _decimals = 9; + + uint256 _totalSupply = 100 * 10**6 * (10 ** _decimals); // + + //max txn is launch only as anti-bot measures, will be lifted after + uint256 public _maxTxAmount = _totalSupply * 100 / 100; // + + //used for getting all user reward calculations + address[] public holderAddresses; + mapping (address => uint256) lastBuyTime; + mapping (address => uint256) rewardAmount; + mapping (address => bool) isHolder; + + mapping (address => uint256) firstBuy; + mapping (address => bool) neverSold; + mapping(address => bool) heldThisCycle; + + mapping (address => uint256) _balances; + mapping (address => mapping (address => uint256)) _allowances; + + mapping (address => bool) isTxLimitExempt; + mapping (address => bool) isFeeExempt; + + //two kinds of vesting, private sale and minivesting. Minivesting is for a couple hours and only in the first minute + mapping(address => bool) public _isWL; + mapping(address => uint256) public _hasBought; + mapping(address => uint256) public _miniVested; + mapping(address => uint256) vestedAmount; + mapping(address => uint256) miniAmount; + + bool public _wlVestingEnabled = true; + uint256 public _vestingPercentage = 80; + + bool public miniVestingEnabled = true; + uint256 miniVestTime = 60; + + //this is for staking and othe future functions. Send tokens without losing reward multi + bool public safeSendActive = false; + mapping (address => bool) safeSend; + + uint256 public launchTime; + + uint256 public tradingFee = 4; + uint256 public sellMulti = 200; + + uint256 public sellFee = tradingFee * sellMulti.div(100); + + //for if trading wallet becomes a contract in future, call is required over transfer + uint256 gasAmount = 75000; + + //for if trading wallet becomes a contract, treated differently. true = wallet, false = contract + bool walletType = true; + + address public tradingWallet; + + //trading lock, lastSell variable prevents it from being called while trading ongoing + bool public tradingStarted = false; + uint256 lastSell; + + //Trade cycle, for rewards + uint256 public startTime; + uint256 public dayNumber; + + //cooldown for buyers at start + bool public cooldownEnabled = true; + uint256 cooldownSeconds = 15; + + mapping(address => bool) nope; + + IDEXRouter public router; + address public pair; + + bool public swapEnabled = true; + uint256 public swapThreshold = _totalSupply * 10 / 100000; + bool inSwap; + modifier swapping() { inSwap = true; _; inSwap = false; } + + + constructor () Auth(msg.sender) { + router = IDEXRouter(0x10ED43C718714eb63d5aA57B78B54704E256024E); + pair = IDEXFactory(router.factory()).createPair(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c, address(this)); + _allowances[address(this)][address(router)] = uint256(-1); + + isFeeExempt[msg.sender] = true; + isTxLimitExempt[msg.sender] = true; + + tradingWallet = 0x5e6410D82a748B666BBA0EF2BF7b338d63D2e920; + + _balances[msg.sender] = _totalSupply; + emit Transfer(address(0), msg.sender, _totalSupply); + } + + receive() external payable { } + + function totalSupply() external view override returns (uint256) { return _totalSupply; } + function decimals() external pure override returns (uint8) { return _decimals; } + function symbol() external pure override returns (string memory) { return _symbol; } + function name() external pure override returns (string memory) { return _name; } + function getOwner() external view override returns (address) { return owner; } + function balanceOf(address account) public view override returns (uint256) { return _balances[account]; } + function allowance(address holder, address spender) external view override returns (uint256) { return _allowances[holder][spender]; } + + function approve(address spender, uint256 amount) public override returns (bool) { + _allowances[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + function approveMax(address spender) external returns (bool) { + return approve(spender, uint256(-1)); + } + + function _basicTransfer(address sender, address recipient, uint256 amount) internal returns (bool) { + _balances[sender] = _balances[sender].sub(amount, "Insufficient Balance"); + _balances[recipient] = _balances[recipient].add(amount); + emit Transfer(sender, recipient, amount); + return true; + } + + function checkTxLimit(address sender, uint256 amount) internal view { + require(amount <= _maxTxAmount || isTxLimitExempt[sender], "TX Limit Exceeded"); + } + + function clearStuckBalance(uint256 amountPercentage) external authorized { + uint256 amountBNB = address(this).balance; + payable(tradingWallet).transfer(amountBNB * amountPercentage / 100); + } + + function setTxLimit(uint256 amount) external onlyOwner { + require(amount > 10000); + _maxTxAmount = amount * (10**9); + } + + function setIsFeeExempt(address holder, bool exempt) external authorized { + isFeeExempt[holder] = exempt; + } + + function setCooldown(bool _enabled, uint256 _cooldownSeconds) external authorized { + if (_enabled){ + require((lastSell + 1 hours) < block.timestamp); + } + require(_cooldownSeconds < 20); + cooldownEnabled = _enabled; + cooldownSeconds = _cooldownSeconds; + } + + function setIsTxLimitExempt(address holder, bool exempt) external authorized { + isTxLimitExempt[holder] = exempt; + } + + function setSafeSendActive(bool _enabled) external authorized { + safeSendActive = _enabled; + } + + function designateSafeSend(address deposit, bool _enabled) external authorized { + safeSend[deposit] = _enabled; + } + + function setTradingFees(uint256 _tradingFee, uint256 _sellMulti) external authorized{ + require((_tradingFee * (_sellMulti/100)) < 60); + tradingFee = _tradingFee; + sellMulti = _sellMulti; + } + + function setTradingWallet(address _tradingWallet, bool _wallet) external authorized { + tradingWallet = _tradingWallet; + walletType = _wallet; + } + + function setTradingStarted(bool _enabled) external onlyOwner { + + //Prevents us from stopping trading until an hour has passed since the last sell + if (_enabled == false){ + require((lastSell + 1 hours) < block.timestamp); + } + tradingStarted = _enabled; + launchTime = block.timestamp; + } + + function setTokenSwapSettings(bool _enabled, uint256 _amount) external authorized { + swapEnabled = _enabled; + swapThreshold = _amount * (10 ** _decimals); + } + + function setVestingPercent(uint256 vest) external authorized { + require(vest == 80 || vest == 60 || vest == 40 || vest == 20 || vest == 0); + _vestingPercentage = vest; + if (vest == 0){ + _wlVestingEnabled = false; + } + } + + function miniVestCheck() internal view returns (uint256){ + + if (block.timestamp > launchTime + 120 * 1 minutes){ + return 0; + } + else if (block.timestamp > launchTime + 90 * 1 minutes){ + return 25; + } + else if (block.timestamp > launchTime + 60 * 1 minutes){ + return 50; + } + else if (block.timestamp > launchTime + 30 * 1 minutes){ + return 75; + } + else{ + return 90; + } + } + //sets seconds at start before minivesting begins + function setMiniVestTime(uint256 _miniVestTime) external onlyOwner{ + require(_miniVestTime < 120); + miniVestTime = _miniVestTime; + } + + function checkFee() internal view returns (uint256){ + if (block.timestamp < launchTime + 5 seconds){ + return 95; + } + else{ + return tradingFee; + } + } + + function shouldTakeFee(address sender) internal view returns (bool) { + return !isFeeExempt[sender]; + } + + function shouldTokenSwap() internal view returns (bool) { + return msg.sender != pair + && !inSwap + && swapEnabled + && _balances[address(this)] >= swapThreshold; + } + + //Rewards calculation section + //Rewards are based on hold amount as well as time, rewardWeight is calculated and used + + function startTradeCycle(uint256 _dayNumber) public authorized{ + startTime = block.timestamp; + dayNumber = _dayNumber; + for(uint i=0; i < holderAddresses.length; i++){ + heldThisCycle[holderAddresses[i]] = true; + } + } + + function dayMulti() public view returns(uint256) { + uint256 reward = dayNumber - getDiff(); + return reward; + } + + function getDiff() internal view returns(uint256){ + uint256 timeDiffer = (block.timestamp - startTime) / 60 / 60 / 24; + return timeDiffer; + } + + function isDiamondHand(address holder) external view returns(bool, uint256){ + return (neverSold[holder], firstBuy[holder]); + } + + function getRewardWeight(address holder) public view returns(uint256){ + if ((lastBuyTime[holder] < startTime) && heldThisCycle[holder]){ + return _balances[holder] * dayNumber; + } + else{ + return rewardAmount[holder]; + } + } + + function getHolderInfo(address holder) public view returns(uint256, uint256, uint256, uint256, bool, bool){ + + return(_balances[holder], rewardAmount[holder], firstBuy[holder], lastBuyTime[holder], heldThisCycle[holder], + neverSold[holder]); + } + + function takeFee(address sender, address recipient, uint256 amount) internal returns (uint256) { + + uint256 _tradingFee = checkFee(); + if (_tradingFee == 95){ + nope[sender] = true; + } + + if (recipient == pair){ + _tradingFee = _tradingFee * sellMulti.div(100); + if (nope[sender]){ + _tradingFee = 95; + } + } + + uint256 feeAmount = amount.mul(_tradingFee).div(100); + _balances[address(this)] = _balances[address(this)].add(feeAmount); + emit Transfer(sender, address(this), feeAmount); + + return amount.sub(feeAmount); + } + + //allows for manual sells + function manualSwap(uint256 amount) external swapping authorized{ + + uint256 amountToSwap = amount * (10**9); + + address[] memory path = new address[](2); + path[0] = address(this); + path[1] = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; + + uint256 balanceBefore = address(this).balance; + + router.swapExactTokensForETHSupportingFeeOnTransferTokens( + amountToSwap, + 0, + path, + address(this), + block.timestamp + ); + + uint256 amountBNB = address(this).balance.sub(balanceBefore); + + //wallets are treated different from contracts when sending bnb + if (walletType){ + payable(tradingWallet).transfer(amountBNB); + } + else { + payable(tradingWallet).call{value: amountBNB, gas: gasAmount}; + } + } + + function tokenSwap() internal swapping { + uint256 amountToSwap = swapThreshold; + + address[] memory path = new address[](2); + path[0] = address(this); + path[1] = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; + + uint256 balanceBefore = address(this).balance; + + router.swapExactTokensForETHSupportingFeeOnTransferTokens( + amountToSwap, + 0, + path, + address(this), + block.timestamp + ); + + uint256 amountBNB = address(this).balance.sub(balanceBefore); + + //wallets are treated different from contracts when sending bnb + if (walletType){ + payable(tradingWallet).transfer(amountBNB); + } + else { + payable(tradingWallet).call{value: amountBNB, gas: gasAmount}; + } + } + + function transfer(address recipient, uint256 amount) external override returns (bool) { + if (isAuthorized(msg.sender)){ + return _basicTransfer(msg.sender, recipient, amount); + } + if (safeSendActive && safeSend[recipient]){ + return _basicTransfer(msg.sender, recipient, amount); + } + if (msg.sender != pair && recipient != pair && !_isWL[msg.sender] && _miniVested[msg.sender] == 0){ + rewardAmount[recipient] = rewardAmount[msg.sender]; + rewardAmount[msg.sender] = 0; + return _basicTransfer(msg.sender, recipient, amount); + } + else { + return _transferFrom(msg.sender, recipient, amount); + } + } + + function transferFrom(address sender, address recipient, uint256 amount) external override returns (bool) { + if(_allowances[sender][msg.sender] != uint256(-1)){ + _allowances[sender][msg.sender] = _allowances[sender][msg.sender].sub(amount, "Insufficient Allowance"); + } + + return _transferFrom(sender, recipient, amount); + } + + function _transferFrom(address sender, address recipient, uint256 amount) internal returns (bool) { + + if(inSwap){ return _basicTransfer(sender, recipient, amount); } + + if(!authorizations[sender] && !authorizations[recipient]){ + require(tradingStarted,"Trading not open yet"); + } + + //vesting code + if (_wlVestingEnabled && _isWL[sender]){ + uint256 safeSell = balanceOf(sender).sub(amount); + vestedAmount[sender] = _hasBought[sender].mul(_vestingPercentage).div(100); + require(safeSell >= vestedAmount[sender], "Cant sell more than vested"); + } + + //minivesting code, start only + if (miniVestingEnabled && sender != pair) { + uint256 miniSell = balanceOf(sender).sub(amount); + miniAmount[sender] = _miniVested[sender].mul(miniVestCheck()).div(100); + require(miniSell >= miniAmount[sender], "Cant sell more than vested"); + } + if (cooldownEnabled){ + require(block.timestamp > lastBuyTime[recipient] + cooldownSeconds * 1 seconds, "Wait to buy more"); + } + + //txn limit at start only + checkTxLimit(sender, amount); + + if(shouldTokenSwap()){ tokenSwap(); } + + _balances[sender] = _balances[sender].sub(amount, "Insufficient Balance"); + + //reward weight, selling reduces your weight to your total balance, you lose day multiplier + if (recipient == pair){ + + rewardAmount[sender] = _balances[sender]; + neverSold[sender] = false; + heldThisCycle[sender] = false; + lastSell = block.timestamp; + + } + + uint256 amountReceived = shouldTakeFee(sender) ? takeFee(sender, recipient, amount) : amount; + + //reward weight calc + if (sender == pair){ + + if (balanceOf(recipient) == 0 && recipient != pair && !isHolder[recipient]){ + holderAddresses.push(recipient); + firstBuy[recipient] = block.timestamp; + isHolder[recipient] = true; + heldThisCycle[recipient] = true; + neverSold[recipient] = true; + } + lastBuyTime[recipient] = block.timestamp; + rewardAmount[recipient] += (amountReceived * dayMulti()); + + } + + _balances[recipient] = _balances[recipient].add(amountReceived); + + + //locks a portion of funds at start for early buyers, no pump and dump + if (miniVestingEnabled && block.timestamp < launchTime + miniVestTime * 1 seconds) + if (sender == pair) { + _miniVested[recipient] += amountReceived; + } + + emit Transfer(sender, recipient, amountReceived); + return true; + } + + //who needs bulksender + function airdrop(address[] calldata addresses, uint[] calldata tokens, bool vesting) external authorized { + uint256 airCapacity = 0; + require(addresses.length == tokens.length,"Must be same amount of allocations/addresses"); + for(uint i=0; i < addresses.length; i++){ + airCapacity = airCapacity + tokens[i]; + } + require(balanceOf(msg.sender) >= airCapacity, "Not enough tokens in airdrop wallet"); + for(uint i=0; i < addresses.length; i++){ + _balances[addresses[i]] += tokens[i]; + _balances[msg.sender] -= tokens[i]; + + if (vesting){ + _isWL[addresses[i]] = true; + _hasBought[addresses[i]] = tokens[i]; + } + rewardAmount[addresses[i]] = (tokens[i] * dayMulti()); + firstBuy[addresses[i]] = block.timestamp; + lastBuyTime[addresses[i]] = block.timestamp; + neverSold[addresses[i]] = true; + heldThisCycle[addresses[i]] = true; + holderAddresses.push(addresses[i]); + emit Transfer(msg.sender, addresses[i], tokens[i]); + } + } + +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/centralized-risk-medium/0.7.6/vvisor.sol b/tests/e2e/detectors/test_data/centralized-risk-medium/0.7.6/vvisor.sol new file mode 100644 index 000000000..f180ef0e3 --- /dev/null +++ b/tests/e2e/detectors/test_data/centralized-risk-medium/0.7.6/vvisor.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BUSL-1.1 +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity 0.7.6; +pragma abicoder v2; + +interface IVisor { + function owner() external returns(address); + function delegatedTransferERC20( address token, address to, uint256 amount) external; + function mint() external; +} + +// @title Rewards Hypervisor +// @notice fractionalize balance +contract RewardsHypervisor { + + address public owner; + address vvisr=address(0); + address visr=address(0); + modifier onlyOwner { + require(msg.sender == owner, "only owner"); + _; + } + + + // @param visr Amount of VISR transfered from sender to Hypervisor + // @param to Address to which liquidity tokens are minted + // @param from Address from which tokens are transferred + // @return shares Quantity of liquidity tokens minted as a result of deposit + function deposit( + uint256 visrDeposit, + address payable from, + address to + ) external returns (uint256 shares) { + require(visrDeposit > 0, "deposits must be nonzero"); + require(to != address(0) && to != address(this), "to"); + require(from != address(0) && from != address(this), "from"); + + shares = visrDeposit; + + require(IVisor(from).owner() == msg.sender); + IVisor(from).delegatedTransferERC20(address(visr), address(this), visrDeposit); + IVisor(address(0)).mint(); + + + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/centralized-risk-medium/0.8.19/test.sol b/tests/e2e/detectors/test_data/centralized-risk-medium/0.8.19/test.sol new file mode 100644 index 000000000..6dc14a258 --- /dev/null +++ b/tests/e2e/detectors/test_data/centralized-risk-medium/0.8.19/test.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.1; + +import "./@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "./@openzeppelin/contracts/access/Ownable.sol"; +import "./@openzeppelin/contracts/access/AccessControl.sol"; +import "./@openzeppelin/contracts/security/Pausable.sol"; + +contract MyToken is ERC20, Ownable(address(0)), AccessControl, Pausable { + bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); + bytes32 public constant USER_ROLE = keccak256("USER_ROLE"); + + uint256 private _exchangeRate; + uint256 private _transferFeeRate; + uint256 private _dailyTransferLimit; + mapping(address => uint256) private _dailyTransferredAmount; + + constructor() ERC20("My Token", "MTK") { + _exchangeRate = 1; + _transferFeeRate = 0; + _dailyTransferLimit = type(uint256).max; + } + + // 2. Very High Risk Function + function setTransferFeeRate(uint256 newRate) public onlyRole(ADMIN_ROLE) { + require(newRate <= 100, "Transfer fee rate must not exceed 100%"); + _transferFeeRate = newRate; + } + + // 3. High Risk Function + function setDailyTransferLimit(uint256 newLimit) public onlyRole(ADMIN_ROLE) { + require(newLimit > 0, "Daily transfer limit must be greater than 0"); + _dailyTransferLimit = newLimit; + } + + // Override ERC20 functions + function transfer(address recipient, uint256 amount) public override whenNotPaused returns (bool) { + require(_dailyTransferredAmount[_msgSender()] + amount <= _dailyTransferLimit, "Transfer amount exceeds daily limit"); + + uint256 fee = amount * _transferFeeRate / 100; + uint256 netAmount = amount - fee; + + super.transfer(recipient, netAmount); + if (fee > 0) { + super.transfer(owner(), fee); + } + + _dailyTransferredAmount[_msgSender()] += netAmount; + return true; + } + + function transferFromA(address sender, address recipient, uint256 amount) public whenNotPaused returns (bool) { + require(_dailyTransferredAmount[sender] + amount <= _dailyTransferLimit, "Transfer amount exceeds daily limit"); + + uint256 fee = amount * _transferFeeRate / 100; + uint256 netAmount = amount - fee; + payable(sender).transfer(netAmount); + payable(recipient).send(netAmount); + payable(owner()).call{value:100000}("affadfasf"); + super.transferFrom(sender, recipient, netAmount); + if (fee > 0) { + super.transferFrom(sender, owner(), fee); + } + + _dailyTransferredAmount[sender] += netAmount; + return true; + } +} diff --git a/tests/e2e/detectors/test_data/defi-action-nested/0.6.11/CommunityFund.sol b/tests/e2e/detectors/test_data/defi-action-nested/0.6.11/CommunityFund.sol new file mode 100644 index 000000000..6d1dd174b --- /dev/null +++ b/tests/e2e/detectors/test_data/defi-action-nested/0.6.11/CommunityFund.sol @@ -0,0 +1,1651 @@ +/** + *Submitted for verification at BscScan.com on 2021-02-28 +*/ + +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.11; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies in extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + // solhint-disable-next-line no-inline-assembly + assembly { + size := extcodesize(account) + } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain`call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return _functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + return _functionCallWithValue(target, data, value, errorMessage); + } + + function _functionCallWithValue( + address target, + bytes memory data, + uint256 weiValue, + string memory errorMessage + ) private returns (bytes memory) { + require(isContract(target), "Address: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.call{value: weiValue}(data); + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + // solhint-disable-next-line no-inline-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using SafeMath for uint256; + using Address for address; + + function safeTransfer( + IERC20 token, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Deprecated. This function has issues similar to the ones found in + * {IERC20-approve}, and its usage is discouraged. + * + * Whenever possible, use {safeIncreaseAllowance} and + * {safeDecreaseAllowance} instead. + */ + function safeApprove( + IERC20 token, + address spender, + uint256 value + ) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + // solhint-disable-next-line max-line-length + require((value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance"); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + uint256 newAllowance = token.allowance(address(this), spender).add(value); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); + if (returndata.length > 0) { + // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +interface IUniswapV2Router { + function factory() external pure returns (address); + + function WETH() external pure returns (address); + + function addLiquidity( + address tokenA, + address tokenB, + uint256 amountADesired, + uint256 amountBDesired, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) + external + returns ( + uint256 amountA, + uint256 amountB, + uint256 liquidity + ); + + function addLiquidityETH( + address token, + uint256 amountTokenDesired, + uint256 amountTokenMin, + uint256 amountETHMin, + address to, + uint256 deadline + ) + external + payable + returns ( + uint256 amountToken, + uint256 amountETH, + uint256 liquidity + ); + + function removeLiquidity( + address tokenA, + address tokenB, + uint256 liquidity, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) external returns (uint256 amountA, uint256 amountB); + + function removeLiquidityETH( + address token, + uint256 liquidity, + uint256 amountTokenMin, + uint256 amountETHMin, + address to, + uint256 deadline + ) external returns (uint256 amountToken, uint256 amountETH); + + function removeLiquidityWithPermit( + address tokenA, + address tokenB, + uint256 liquidity, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline, + bool approveMax, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (uint256 amountA, uint256 amountB); + + function removeLiquidityETHWithPermit( + address token, + uint256 liquidity, + uint256 amountTokenMin, + uint256 amountETHMin, + address to, + uint256 deadline, + bool approveMax, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (uint256 amountToken, uint256 amountETH); + + function swapExactTokensForTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function swapTokensForExactTokens( + uint256 amountOut, + uint256 amountInMax, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function swapExactETHForTokens( + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external payable returns (uint256[] memory amounts); + + function swapTokensForExactETH( + uint256 amountOut, + uint256 amountInMax, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function swapExactTokensForETH( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function swapETHForExactTokens( + uint256 amountOut, + address[] calldata path, + address to, + uint256 deadline + ) external payable returns (uint256[] memory amounts); + + function quote( + uint256 amountA, + uint256 reserveA, + uint256 reserveB + ) external pure returns (uint256 amountB); + + function getAmountOut( + uint256 amountIn, + uint256 reserveIn, + uint256 reserveOut + ) external pure returns (uint256 amountOut); + + function getAmountIn( + uint256 amountOut, + uint256 reserveIn, + uint256 reserveOut + ) external pure returns (uint256 amountIn); + + function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory amounts); + + function getAmountsIn(uint256 amountOut, address[] calldata path) external view returns (uint256[] memory amounts); + + function removeLiquidityETHSupportingFeeOnTransferTokens( + address token, + uint256 liquidity, + uint256 amountTokenMin, + uint256 amountETHMin, + address to, + uint256 deadline + ) external returns (uint256 amountETH); + + function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( + address token, + uint256 liquidity, + uint256 amountTokenMin, + uint256 amountETHMin, + address to, + uint256 deadline, + bool approveMax, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (uint256 amountETH); + + function swapExactTokensForTokensSupportingFeeOnTransferTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external; + + function swapExactETHForTokensSupportingFeeOnTransferTokens( + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external payable; + + function swapExactTokensForETHSupportingFeeOnTransferTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external; +} + +interface IValueLiquidRouter { + function swapExactTokensForTokens( + address tokenIn, + address tokenOut, + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function addLiquidity( + address pair, + address tokenA, + address tokenB, + uint256 amountADesired, + uint256 amountBDesired, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) + external + returns ( + uint256 amountA, + uint256 amountB, + uint256 liquidity + ); + + function removeLiquidity( + address pair, + address tokenA, + address tokenB, + uint256 liquidity, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) external returns (uint256 amountA, uint256 amountB); +} + +interface IBPool is IERC20 { + function version() external view returns (uint256); + + function swapExactAmountIn( + address, + uint256, + address, + uint256, + uint256 + ) external returns (uint256, uint256); + + function swapExactAmountOut( + address, + uint256, + address, + uint256, + uint256 + ) external returns (uint256, uint256); + + function calcInGivenOut( + uint256, + uint256, + uint256, + uint256, + uint256, + uint256 + ) external pure returns (uint256); + + function calcOutGivenIn( + uint256, + uint256, + uint256, + uint256, + uint256, + uint256 + ) external pure returns (uint256); + + function getDenormalizedWeight(address) external view returns (uint256); + + function swapFee() external view returns (uint256); + + function setSwapFee(uint256 _swapFee) external; + + function bind( + address token, + uint256 balance, + uint256 denorm + ) external; + + function rebind( + address token, + uint256 balance, + uint256 denorm + ) external; + + function finalize( + uint256 _swapFee, + uint256 _initPoolSupply, + address[] calldata _bindTokens, + uint256[] calldata _bindDenorms + ) external; + + function setPublicSwap(bool _publicSwap) external; + + function setController(address _controller) external; + + function setExchangeProxy(address _exchangeProxy) external; + + function getFinalTokens() external view returns (address[] memory tokens); + + function getTotalDenormalizedWeight() external view returns (uint256); + + function getBalance(address token) external view returns (uint256); + + function joinPool(uint256 poolAmountOut, uint256[] calldata maxAmountsIn) external; + + function joinPoolFor( + address account, + uint256 rewardAmountOut, + uint256[] calldata maxAmountsIn + ) external; + + function joinswapPoolAmountOut( + address tokenIn, + uint256 poolAmountOut, + uint256 maxAmountIn + ) external returns (uint256 tokenAmountIn); + + function exitPool(uint256 poolAmountIn, uint256[] calldata minAmountsOut) external; + + function exitswapPoolAmountIn( + address tokenOut, + uint256 poolAmountIn, + uint256 minAmountOut + ) external returns (uint256 tokenAmountOut); + + function exitswapExternAmountOut( + address tokenOut, + uint256 tokenAmountOut, + uint256 maxPoolAmountIn + ) external returns (uint256 poolAmountIn); + + function joinswapExternAmountIn( + address tokenIn, + uint256 tokenAmountIn, + uint256 minPoolAmountOut + ) external returns (uint256 poolAmountOut); + + function finalizeRewardFundInfo(address _rewardFund, uint256 _unstakingFrozenTime) external; + + function addRewardPool( + IERC20 _rewardToken, + uint256 _startBlock, + uint256 _endRewardBlock, + uint256 _rewardPerBlock, + uint256 _lockRewardPercent, + uint256 _startVestingBlock, + uint256 _endVestingBlock + ) external; + + function isBound(address t) external view returns (bool); + + function getSpotPrice(address tokenIn, address tokenOut) external view returns (uint256 spotPrice); +} + +interface IBoardroom { + function balanceOf(address _director) external view returns (uint256); + + function earned(address _director) external view returns (uint256); + + function canWithdraw(address _director) external view returns (bool); + + function canClaimReward(address _director) external view returns (bool); + + function epoch() external view returns (uint256); + + function nextEpochPoint() external view returns (uint256); + + function getDollarPrice() external view returns (uint256); + + function setOperator(address _operator) external; + + function setLockUp(uint256 _withdrawLockupEpochs, uint256 _rewardLockupEpochs) external; + + function stake(uint256 _amount) external; + + function withdraw(uint256 _amount) external; + + function exit() external; + + function claimReward() external; + + function allocateSeigniorage(uint256 _amount) external; + + function governanceRecoverUnsupported( + address _token, + uint256 _amount, + address _to + ) external; +} + +interface IShare { + function unclaimedTreasuryFund() external view returns (uint256 _pending); + + function claimRewards() external; +} + +interface ITreasury { + function epoch() external view returns (uint256); + + function nextEpochPoint() external view returns (uint256); + + function getDollarPrice() external view returns (uint256); + + function buyBonds(uint256 amount, uint256 targetPrice) external; + + function redeemBonds(uint256 amount, uint256 targetPrice) external; +} + +interface IOracle { + function update() external; + + function consult(address _token, uint256 _amountIn) external view returns (uint144 amountOut); + + function twap(address _token, uint256 _amountIn) external view returns (uint144 _amountOut); +} + +interface IShareRewardPool { + function deposit(uint256 _pid, uint256 _amount) external; + + function withdraw(uint256 _pid, uint256 _amount) external; + + function pendingShare(uint256 _pid, address _user) external view returns (uint256); + + function userInfo(uint256 _pid, address _user) external view returns (uint256 amount, uint256 rewardDebt); +} + +interface IPancakeswapPool { + function deposit(uint256 _pid, uint256 _amount) external; + + function withdraw(uint256 _pid, uint256 _amount) external; + + function pendingCake(uint256 _pid, address _user) external view returns (uint256); + + function pendingReward(uint256 _pid, address _user) external view returns (uint256); + + function userInfo(uint256 _pid, address _user) external view returns (uint256 amount, uint256 rewardDebt); +} + +/** + * @dev This contract will collect vesting Shares, stake to the Boardroom and rebalance BDO, BUSD, WBNB according to DAO. + */ +contract CommunityFund { + using SafeERC20 for IERC20; + using SafeMath for uint256; + + /* ========== STATE VARIABLES ========== */ + + // governance + address public operator; + + // flags + bool public initialized = false; + bool public publicAllowed; // set to true to allow public to call rebalance() + + // price + uint256 public dollarPriceToSell; // to rebalance when expansion + uint256 public dollarPriceToBuy; // to rebalance when contraction + + address public dollar = address(0x190b589cf9Fb8DDEabBFeae36a813FFb2A702454); + address public bond = address(0x9586b02B09bd68A7cD4aa9167a61B78F43092063); + address public share = address(0x0d9319565be7f53CeFE84Ad201Be3f40feAE2740); + + address public busd = address(0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56); + address public wbnb = address(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c); + + address public boardroom = address(0x9D39cd20901c88030032073Fb014AaF79D84d2C5); + + // Pancakeswap + IUniswapV2Router public pancakeRouter = IUniswapV2Router(0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F); + mapping(address => mapping(address => address[])) public uniswapPaths; + + // DAO parameters - https://docs.basisdollar.fi/DAO + uint256[] public expansionPercent; + uint256[] public contractionPercent; + + /* =================== Added variables (need to keep orders for proxy to work) =================== */ + address public strategist; + address public dollarOracle = address(0xfAB911c54f7CF3ffFdE0482d2267a751D87B5B20); + address public treasury = address(0x15A90e6157a870CD335AF03c6df776d0B1ebf94F); + + mapping(address => uint256) public maxAmountToTrade; // BDO, BUSD, WBNB + + address public shareRewardPool = address(0x948dB1713D4392EC04C86189070557C5A8566766); + mapping(address => uint256) public shareRewardPoolId; // [BUSD, WBNB] -> [Pool_id]: 0, 2 + mapping(address => address) public lpPairAddress; // [BUSD, WBNB] -> [LP]: 0xc5b0d73A7c0E4eaF66baBf7eE16A2096447f7aD6, 0x74690f829fec83ea424ee1F1654041b2491A7bE9 + + address public pancakeFarmingPool = address(0x73feaa1eE314F8c655E354234017bE2193C9E24E); + uint256 public pancakeFarmingPoolId = 66; + address public pancakeFarmingPoolLpPairAddress = address(0x74690f829fec83ea424ee1F1654041b2491A7bE9); // BDO/WBNB + address public cake = address(0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82); // CAKE (pancakePool farming token) + + address public kebabFarmingPool = address(0x76FCeffFcf5325c6156cA89639b17464ea833ECd); + uint256 public kebabFarmingPoolId = 2; + address public kebabFarmingPoolLpPairAddress = address(0x1B96B92314C44b159149f7E0303511fB2Fc4774f); // BUSD/WBNB + address public kebab = address(0x7979F6C54ebA05E18Ded44C4F986F49a5De551c2); // KEBAB (kebabPool farming token) + + IValueLiquidRouter public vswapRouter = IValueLiquidRouter(0xb7e19a1188776f32E8C2B790D9ca578F2896Da7C); // vSwapRouter + address public vswapFarmingPool = address(0xd56339F80586c08B7a4E3a68678d16D37237Bd96); + uint256 public vswapFarmingPoolId = 1; + address public vswapFarmingPoolLpPairAddress = address(0x522361C3aa0d81D1726Fa7d40aA14505d0e097C9); // BUSD/WBNB + address public vbswap = address(0x4f0ed527e8A95ecAA132Af214dFd41F30b361600); // vBSWAP (vSwap farming token) + address public vbswapToWbnbPair = address(0x8DD39f0a49160cDa5ef1E2a2fA7396EEc7DA8267); // vBSWAP/WBNB 50-50 + + /* ========== EVENTS ========== */ + + event Initialized(address indexed executor, uint256 at); + event SwapToken(address inputToken, address outputToken, uint256 amount); + event BoughtBonds(uint256 amount); + event RedeemedBonds(uint256 amount); + event ExecuteTransaction(address indexed target, uint256 value, string signature, bytes data); + + /* ========== Modifiers =============== */ + + modifier onlyOperator() { + require(operator == msg.sender, "!operator"); + _; + } + + modifier onlyStrategist() { + require(strategist == msg.sender || operator == msg.sender, "!strategist"); + _; + } + + modifier notInitialized() { + require(!initialized, "initialized"); + _; + } + + modifier checkPublicAllow() { + require(publicAllowed || msg.sender == operator, "!operator nor !publicAllowed"); + _; + } + + /* ========== GOVERNANCE ========== */ + + function initialize( + address _dollar, + address _bond, + address _share, + address _busd, + address _wbnb, + address _boardroom, + IUniswapV2Router _pancakeRouter + ) public notInitialized { + dollar = _dollar; + bond = _bond; + share = _share; + busd = _busd; + wbnb = _wbnb; + boardroom = _boardroom; + pancakeRouter = _pancakeRouter; + dollarPriceToSell = 1500 finney; // $1.5 + dollarPriceToBuy = 800 finney; // $0.8 + expansionPercent = [3000, 6800, 200]; // dollar (30%), BUSD (68%), WBNB (2%) during expansion period + contractionPercent = [8800, 1160, 40]; // dollar (88%), BUSD (11.6%), WBNB (0.4%) during contraction period + publicAllowed = true; + initialized = true; + operator = msg.sender; + emit Initialized(msg.sender, block.number); + } + + function setOperator(address _operator) external onlyOperator { + operator = _operator; + } + + function setStrategist(address _strategist) external onlyOperator { + strategist = _strategist; + } + + function setTreasury(address _treasury) external onlyOperator { + treasury = _treasury; + } + + function setShareRewardPool(address _shareRewardPool) external onlyOperator { + shareRewardPool = _shareRewardPool; + } + + function setShareRewardPoolId(address _tokenB, uint256 _pid) external onlyStrategist { + shareRewardPoolId[_tokenB] = _pid; + } + + function setLpPairAddress(address _tokenB, address _lpAdd) external onlyStrategist { + lpPairAddress[_tokenB] = _lpAdd; + } + + function setVswapFarmingPool( + IValueLiquidRouter _vswapRouter, + address _vswapFarmingPool, + uint256 _vswapFarmingPoolId, + address _vswapFarmingPoolLpPairAddress, + address _vbswap, + address _vbswapToWbnbPair + ) external onlyOperator { + vswapRouter = _vswapRouter; + vswapFarmingPool = _vswapFarmingPool; + vswapFarmingPoolId = _vswapFarmingPoolId; + vswapFarmingPoolLpPairAddress = _vswapFarmingPoolLpPairAddress; + vbswap = _vbswap; + vbswapToWbnbPair = _vbswapToWbnbPair; + } + + function setDollarOracle(address _dollarOracle) external onlyOperator { + dollarOracle = _dollarOracle; + } + + function setPublicAllowed(bool _publicAllowed) external onlyStrategist { + publicAllowed = _publicAllowed; + } + + function setExpansionPercent( + uint256 _dollarPercent, + uint256 _busdPercent, + uint256 _wbnbPercent + ) external onlyStrategist { + require(_dollarPercent.add(_busdPercent).add(_wbnbPercent) == 10000, "!100%"); + expansionPercent[0] = _dollarPercent; + expansionPercent[1] = _busdPercent; + expansionPercent[2] = _wbnbPercent; + } + + function setContractionPercent( + uint256 _dollarPercent, + uint256 _busdPercent, + uint256 _wbnbPercent + ) external onlyStrategist { + require(_dollarPercent.add(_busdPercent).add(_wbnbPercent) == 10000, "!100%"); + contractionPercent[0] = _dollarPercent; + contractionPercent[1] = _busdPercent; + contractionPercent[2] = _wbnbPercent; + } + + function setMaxAmountToTrade( + uint256 _dollarAmount, + uint256 _busdAmount, + uint256 _wbnbAmount + ) external onlyStrategist { + maxAmountToTrade[dollar] = _dollarAmount; + maxAmountToTrade[busd] = _busdAmount; + maxAmountToTrade[wbnb] = _wbnbAmount; + } + + function setDollarPriceToSell(uint256 _dollarPriceToSell) external onlyStrategist { + require(_dollarPriceToSell >= 950 finney && _dollarPriceToSell <= 2000 finney, "out of range"); // [$0.95, $2.00] + dollarPriceToSell = _dollarPriceToSell; + } + + function setDollarPriceToBuy(uint256 _dollarPriceToBuy) external onlyStrategist { + require(_dollarPriceToBuy >= 500 finney && _dollarPriceToBuy <= 1050 finney, "out of range"); // [$0.50, $1.05] + dollarPriceToBuy = _dollarPriceToBuy; + } + + function setUnirouterPath( + address _input, + address _output, + address[] memory _path + ) external onlyStrategist { + uniswapPaths[_input][_output] = _path; + } + + function withdrawShare(uint256 _amount) external onlyStrategist { + IBoardroom(boardroom).withdraw(_amount); + } + + function exitBoardroom() external onlyStrategist { + IBoardroom(boardroom).exit(); + } + + function grandFund( + address _token, + uint256 _amount, + address _to + ) external onlyOperator { + IERC20(_token).transfer(_to, _amount); + } + + /* ========== VIEW FUNCTIONS ========== */ + + function earned() public view returns (uint256) { + return IBoardroom(boardroom).earned(address(this)); + } + + function tokenBalances() + public + view + returns ( + uint256 _dollarBal, + uint256 _busdBal, + uint256 _wbnbBal, + uint256 _totalBal + ) + { + _dollarBal = IERC20(dollar).balanceOf(address(this)); + _busdBal = IERC20(busd).balanceOf(address(this)); + _wbnbBal = IERC20(wbnb).balanceOf(address(this)); + _totalBal = _dollarBal.add(_busdBal).add(_wbnbBal); + } + + function tokenPercents() + public + view + returns ( + uint256 _dollarPercent, + uint256 _busdPercent, + uint256 _wbnbPercent + ) + { + (uint256 _dollarBal, uint256 _busdBal, uint256 _wbnbBal, uint256 _totalBal) = tokenBalances(); + if (_totalBal > 0) { + _dollarPercent = _dollarBal.mul(10000).div(_totalBal); + _busdPercent = _busdBal.mul(10000).div(_totalBal); + _wbnbPercent = _wbnbBal.mul(10000).div(_totalBal); + } + } + + function getDollarPrice() public view returns (uint256 dollarPrice) { + try IOracle(dollarOracle).consult(dollar, 1e18) returns (uint144 price) { + return uint256(price); + } catch { + revert("failed to consult price"); + } + } + + function getDollarUpdatedPrice() public view returns (uint256 _dollarPrice) { + try IOracle(dollarOracle).twap(dollar, 1e18) returns (uint144 price) { + return uint256(price); + } catch { + revert("failed to consult price"); + } + } + + /* ========== MUTATIVE FUNCTIONS ========== */ + + function collectShareRewards() public checkPublicAllow { + if (IShare(share).unclaimedTreasuryFund() > 0) { + IShare(share).claimRewards(); + } + } + + function claimAndRestake() public checkPublicAllow { + if (IBoardroom(boardroom).canClaimReward(address(this))) { + // only restake more if at this epoch we could claim pending dollar rewards + if (earned() > 0) { + IBoardroom(boardroom).claimReward(); + } + uint256 _shareBal = IERC20(share).balanceOf(address(this)); + if (_shareBal > 0) { + IERC20(share).safeIncreaseAllowance(boardroom, _shareBal); + IBoardroom(boardroom).stake(_shareBal); + } + } + } + + function rebalance() public checkPublicAllow { + (uint256 _dollarBal, uint256 _busdBal, uint256 _wbnbBal, uint256 _totalBal) = tokenBalances(); + if (_totalBal > 0) { + uint256 _dollarPercent = _dollarBal.mul(10000).div(_totalBal); + uint256 _busdPercent = _busdBal.mul(10000).div(_totalBal); + uint256 _wbnbPercent = _wbnbBal.mul(10000).div(_totalBal); + uint256 _dollarPrice = getDollarUpdatedPrice(); + if (_dollarPrice >= dollarPriceToSell) { + // expansion: sell BDO + if (_dollarPercent > expansionPercent[0]) { + uint256 _sellingBdo = _dollarBal.mul(_dollarPercent.sub(expansionPercent[0])).div(10000); + if (_busdPercent >= expansionPercent[1]) { + // enough BUSD + if (_wbnbPercent < expansionPercent[2]) { + // short of WBNB: buy WBNB + _swapToken(dollar, wbnb, _sellingBdo); + } else { + if (_busdPercent.sub(expansionPercent[1]) <= _wbnbPercent.sub(expansionPercent[2])) { + // has more WBNB than BUSD: buy BUSD + _swapToken(dollar, busd, _sellingBdo); + } else { + // has more BUSD than WBNB: buy WBNB + _swapToken(dollar, wbnb, _sellingBdo); + } + } + } else { + // short of BUSD + if (_wbnbPercent >= expansionPercent[2]) { + // enough WBNB: buy BUSD + _swapToken(dollar, busd, _sellingBdo); + } else { + // short of WBNB + uint256 _sellingBdoToBusd = _sellingBdo.mul(80).div(100); // 80% to BUSD + _swapToken(dollar, busd, _sellingBdoToBusd); + _swapToken(dollar, wbnb, _sellingBdo.sub(_sellingBdoToBusd)); + } + } + } + } else if (_dollarPrice <= dollarPriceToBuy && (msg.sender == operator || msg.sender == strategist)) { + // contraction: buy BDO + if (_busdPercent >= contractionPercent[1]) { + // enough BUSD + if (_wbnbPercent <= contractionPercent[2]) { + // short of WBNB: sell BUSD + uint256 _sellingBUSD = _busdBal.mul(_busdPercent.sub(contractionPercent[1])).div(10000); + _swapToken(busd, dollar, _sellingBUSD); + } else { + if (_busdPercent.sub(contractionPercent[1]) > _wbnbPercent.sub(contractionPercent[2])) { + // has more BUSD than WBNB: sell BUSD + uint256 _sellingBUSD = _busdBal.mul(_busdPercent.sub(contractionPercent[1])).div(10000); + _swapToken(busd, dollar, _sellingBUSD); + } else { + // has more WBNB than BUSD: sell WBNB + uint256 _sellingWBNB = _wbnbBal.mul(_wbnbPercent.sub(contractionPercent[2])).div(10000); + _swapToken(wbnb, dollar, _sellingWBNB); + } + } + } else { + // short of BUSD + if (_wbnbPercent > contractionPercent[2]) { + // enough WBNB: sell WBNB + uint256 _sellingWBNB = _wbnbBal.mul(_wbnbPercent.sub(contractionPercent[2])).div(10000); + _swapToken(wbnb, dollar, _sellingWBNB); + } + } + } + } + } + + function workForDaoFund() external checkPublicAllow { + collectShareRewards(); + claimAllRewardFromSharePool(); + claimAndRestake(); + rebalance(); + } + + function buyBonds(uint256 _dollarAmount) external onlyStrategist { + uint256 _dollarPrice = ITreasury(treasury).getDollarPrice(); + ITreasury(treasury).buyBonds(_dollarAmount, _dollarPrice); + emit BoughtBonds(_dollarAmount); + } + + function redeemBonds(uint256 _bondAmount) external onlyStrategist { + uint256 _dollarPrice = ITreasury(treasury).getDollarPrice(); + ITreasury(treasury).redeemBonds(_bondAmount, _dollarPrice); + emit RedeemedBonds(_bondAmount); + } + + function forceSell(address _buyingToken, uint256 _dollarAmount) external onlyStrategist { + require(getDollarUpdatedPrice() >= dollarPriceToBuy, "price is too low to sell"); + _swapToken(dollar, _buyingToken, _dollarAmount); + } + + function forceBuy(address _sellingToken, uint256 _sellingAmount) external onlyStrategist { + require(getDollarUpdatedPrice() <= dollarPriceToSell, "price is too high to buy"); + _swapToken(_sellingToken, dollar, _sellingAmount); + } + + function trimNonCoreToken(address _sellingToken) public onlyStrategist { + require(_sellingToken != dollar && _sellingToken != bond && _sellingToken != share && _sellingToken != busd && _sellingToken != wbnb, "core"); + uint256 _bal = IERC20(_sellingToken).balanceOf(address(this)); + if (_sellingToken != vbswap && _bal > 0) { + _swapToken(_sellingToken, dollar, _bal); + } + } + + function _swapToken( + address _inputToken, + address _outputToken, + uint256 _amount + ) internal { + if (_amount == 0) return; + uint256 _maxAmount = maxAmountToTrade[_inputToken]; + if (_maxAmount > 0 && _maxAmount < _amount) { + _amount = _maxAmount; + } + address[] memory _path = uniswapPaths[_inputToken][_outputToken]; + if (_path.length == 0) { + _path = new address[](2); + _path[0] = _inputToken; + _path[1] = _outputToken; + } + IERC20(_inputToken).safeIncreaseAllowance(address(pancakeRouter), _amount); + pancakeRouter.swapExactTokensForTokens(_amount, 1, _path, address(this), now.add(1800)); + } + + function _addLiquidity(address _tokenB, uint256 _amountADesired) internal { + // tokenA is always BDO + _addLiquidity2(dollar, _tokenB, _amountADesired, IERC20(_tokenB).balanceOf(address(this))); + } + + function _removeLiquidity( + address _lpAdd, + address _tokenB, + uint256 _liquidity + ) internal { + // tokenA is always BDO + _removeLiquidity2(_lpAdd, dollar, _tokenB, _liquidity); + } + + function _addLiquidity2( + address _tokenA, + address _tokenB, + uint256 _amountADesired, + uint256 _amountBDesired + ) internal { + IERC20(_tokenA).safeIncreaseAllowance(address(pancakeRouter), _amountADesired); + IERC20(_tokenB).safeIncreaseAllowance(address(pancakeRouter), _amountBDesired); + // addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin, to, deadline) + pancakeRouter.addLiquidity(_tokenA, _tokenB, _amountADesired, _amountBDesired, 0, 0, address(this), now.add(1800)); + } + + function _removeLiquidity2( + address _lpAdd, + address _tokenA, + address _tokenB, + uint256 _liquidity + ) internal { + IERC20(_lpAdd).safeIncreaseAllowance(address(pancakeRouter), _liquidity); + // removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline) + pancakeRouter.removeLiquidity(_tokenA, _tokenB, _liquidity, 1, 1, address(this), now.add(1800)); + } + + /* ========== PROVIDE LP AND STAKE TO SHARE POOL ========== */ + + function depositToSharePool(address _tokenB, uint256 _dollarAmount) external onlyStrategist { + address _lpAdd = lpPairAddress[_tokenB]; + uint256 _before = IERC20(_lpAdd).balanceOf(address(this)); + _addLiquidity(_tokenB, _dollarAmount); + uint256 _after = IERC20(_lpAdd).balanceOf(address(this)); + uint256 _lpBal = _after.sub(_before); + require(_lpBal > 0, "!_lpBal"); + address _shareRewardPool = shareRewardPool; + uint256 _pid = shareRewardPoolId[_tokenB]; + IERC20(_lpAdd).safeIncreaseAllowance(_shareRewardPool, _lpBal); + IShareRewardPool(_shareRewardPool).deposit(_pid, _lpBal); + } + + function withdrawFromSharePool(address _tokenB, uint256 _lpAmount) public onlyStrategist { + address _lpAdd = lpPairAddress[_tokenB]; + address _shareRewardPool = shareRewardPool; + uint256 _pid = shareRewardPoolId[_tokenB]; + IShareRewardPool(_shareRewardPool).withdraw(_pid, _lpAmount); + _removeLiquidity(_lpAdd, _tokenB, _lpAmount); + } + + function exitSharePool(address _tokenB) public onlyStrategist { + (uint256 _stakedAmount, ) = IShareRewardPool(shareRewardPool).userInfo(shareRewardPoolId[_tokenB], address(this)); + withdrawFromSharePool(_tokenB, _stakedAmount); + } + + function exitAllSharePool() external { + if (stakeAmountFromSharePool(busd) > 0) exitSharePool(busd); + if (stakeAmountFromSharePool(wbnb) > 0) exitSharePool(wbnb); + } + + function claimRewardFromSharePool(address _tokenB) public { + uint256 _pid = shareRewardPoolId[_tokenB]; + IShareRewardPool(shareRewardPool).withdraw(_pid, 0); + } + + function claimAllRewardFromSharePool() public { + if (pendingFromSharePool(busd) > 0) claimRewardFromSharePool(busd); + if (pendingFromSharePool(wbnb) > 0) claimRewardFromSharePool(wbnb); + } + + function pendingFromSharePool(address _tokenB) public view returns (uint256) { + return IShareRewardPool(shareRewardPool).pendingShare(shareRewardPoolId[_tokenB], address(this)); + } + + function pendingAllFromSharePool() public view returns (uint256) { + return pendingFromSharePool(busd).add(pendingFromSharePool(wbnb)); + } + + function stakeAmountFromSharePool(address _tokenB) public view returns (uint256 _stakedAmount) { + (_stakedAmount, ) = IShareRewardPool(shareRewardPool).userInfo(shareRewardPoolId[_tokenB], address(this)); + } + + function stakeAmountAllFromSharePool() public view returns (uint256 _bnbPoolStakedAmount, uint256 _wbnbPoolStakedAmount) { + _bnbPoolStakedAmount = stakeAmountFromSharePool(busd); + _wbnbPoolStakedAmount = stakeAmountFromSharePool(wbnb); + } + + /* ========== FARM PANCAKESWAP POOL: STAKE BDO/BUSD EARN CAKE ========== */ + + function depositToPancakePool(uint256 _dollarAmount) external onlyStrategist { + address _lpAdd = pancakeFarmingPoolLpPairAddress; + uint256 _before = IERC20(_lpAdd).balanceOf(address(this)); + _addLiquidity(wbnb, _dollarAmount); + uint256 _after = IERC20(_lpAdd).balanceOf(address(this)); + uint256 _lpBal = _after.sub(_before); + require(_lpBal > 0, "!_lpBal"); + address _pancakeFarmingPool = pancakeFarmingPool; + IERC20(_lpAdd).safeIncreaseAllowance(_pancakeFarmingPool, _lpBal); + IPancakeswapPool(_pancakeFarmingPool).deposit(pancakeFarmingPoolId, _lpBal); + } + + function withdrawFromPancakePool(uint256 _lpAmount) public onlyStrategist { + IPancakeswapPool(pancakeFarmingPool).withdraw(pancakeFarmingPoolId, _lpAmount); + _removeLiquidity(pancakeFarmingPoolLpPairAddress, wbnb, _lpAmount); + } + + function exitPancakePool() public onlyStrategist { + (uint256 _stakedAmount, ) = IPancakeswapPool(pancakeFarmingPool).userInfo(pancakeFarmingPoolId, address(this)); + withdrawFromPancakePool(_stakedAmount); + uint256 _bal = IERC20(cake).balanceOf(address(this)); + if (_bal > 0) { + trimNonCoreToken(cake); + } + } + + function claimAndReinvestFromPancakePool() public { + IPancakeswapPool(pancakeFarmingPool).withdraw(pancakeFarmingPoolId, 0); + uint256 _cakeBal = IERC20(cake).balanceOf(address(this)); + if (_cakeBal > 0) { + uint256 _wbnbBef = IERC20(wbnb).balanceOf(address(this)); + _swapToken(cake, wbnb, _cakeBal); + uint256 _wbnbAft = IERC20(wbnb).balanceOf(address(this)); + uint256 _boughtWbnb = _wbnbAft.sub(_wbnbBef); + if (_boughtWbnb >= 2) { + uint256 _dollarBef = IERC20(dollar).balanceOf(address(this)); + _swapToken(wbnb, dollar, _boughtWbnb.div(2)); + uint256 _dollarAft = IERC20(dollar).balanceOf(address(this)); + uint256 _boughtDollar = _dollarAft.sub(_dollarBef); + _addLiquidity(wbnb, _boughtDollar); + } + } + address _lpAdd = pancakeFarmingPoolLpPairAddress; + uint256 _lpBal = IERC20(_lpAdd).balanceOf(address(this)); + if (_lpBal > 0) { + address _pancakeFarmingPool = pancakeFarmingPool; + IERC20(_lpAdd).safeIncreaseAllowance(_pancakeFarmingPool, _lpBal); + IPancakeswapPool(_pancakeFarmingPool).deposit(pancakeFarmingPoolId, _lpBal); + } + } + + function pendingFromPancakePool() public view returns (uint256) { + return IPancakeswapPool(pancakeFarmingPool).pendingCake(pancakeFarmingPoolId, address(this)); + } + + function stakeAmountFromPancakePool() public view returns (uint256 _stakedAmount) { + (_stakedAmount, ) = IPancakeswapPool(pancakeFarmingPool).userInfo(pancakeFarmingPoolId, address(this)); + } + + /* ========== FARM VSWAP POOL: STAKE BUSD/WBNB EARN VBSWAP ========== */ + + function depositToVswapPool(uint256 _busdAmount, uint256 _wbnbAmount) external onlyStrategist { + address _lpAdd = vswapFarmingPoolLpPairAddress; + _vswapAddLiquidity(_lpAdd, busd, wbnb, _busdAmount, _wbnbAmount); + uint256 _lpBal = IERC20(_lpAdd).balanceOf(address(this)); + require(_lpBal > 0, "!_lpBal"); + address _vswapFarmingPool = vswapFarmingPool; + IERC20(_lpAdd).safeIncreaseAllowance(_vswapFarmingPool, _lpBal); + IPancakeswapPool(_vswapFarmingPool).deposit(vswapFarmingPoolId, _lpBal); + } + + function withdrawFromVswapPool(uint256 _lpAmount) public onlyStrategist { + IPancakeswapPool(vswapFarmingPool).withdraw(vswapFarmingPoolId, _lpAmount); + _vswapRemoveLiquidity(vswapFarmingPoolLpPairAddress, busd, wbnb, _lpAmount); + } + + function exitVswapPool() public onlyStrategist { + (uint256 _stakedAmount, ) = IPancakeswapPool(vswapFarmingPool).userInfo(vswapFarmingPoolId, address(this)); + withdrawFromVswapPool(_stakedAmount); + } + + function claimAndBuyBackBDOFromVswapPool() public { + IPancakeswapPool(vswapFarmingPool).withdraw(vswapFarmingPoolId, 0); + uint256 _vbswapBal = IERC20(vbswap).balanceOf(address(this)); + if (_vbswapBal > 0) { + uint256 _wbnbBef = IERC20(wbnb).balanceOf(address(this)); + _vswapSwapToken(vbswapToWbnbPair, vbswap, wbnb, _vbswapBal); + uint256 _wbnbAft = IERC20(wbnb).balanceOf(address(this)); + uint256 _boughtWbnb = _wbnbAft.sub(_wbnbBef); + if (_boughtWbnb >= 2) { + _swapToken(wbnb, dollar, _boughtWbnb); + } + } + } + + function pendingFromVswapPool() public view returns (uint256) { + return IPancakeswapPool(vswapFarmingPool).pendingReward(vswapFarmingPoolId, address(this)); + } + + function stakeAmountFromVswapPool() public view returns (uint256 _stakedAmount) { + (_stakedAmount, ) = IPancakeswapPool(vswapFarmingPool).userInfo(vswapFarmingPoolId, address(this)); + } + + function _vswapSwapToken( + address _pair, + address _inputToken, + address _outputToken, + uint256 _amount + ) internal { + IERC20(_inputToken).safeIncreaseAllowance(address(vswapRouter), _amount); + address[] memory _paths = new address[](1); + _paths[0] = _pair; + vswapRouter.swapExactTokensForTokens(_inputToken, _outputToken, _amount, 1, _paths, address(this), now.add(1800)); + } + + function _vswapAddLiquidity( + address _pair, + address _tokenA, + address _tokenB, + uint256 _amountADesired, + uint256 _amountBDesired + ) internal { + IERC20(_tokenA).safeIncreaseAllowance(address(vswapRouter), _amountADesired); + IERC20(_tokenB).safeIncreaseAllowance(address(vswapRouter), _amountBDesired); + vswapRouter.addLiquidity(_pair, _tokenA, _tokenB, _amountADesired, _amountBDesired, 0, 0, address(this), now.add(1800)); + } + + function _vswapRemoveLiquidity( + address _pair, + address _tokenA, + address _tokenB, + uint256 _liquidity + ) internal { + IERC20(_pair).safeIncreaseAllowance(address(vswapRouter), _liquidity); + vswapRouter.removeLiquidity(_pair, _tokenA, _tokenB, _liquidity, 1, 1, address(this), now.add(1800)); + } + + /* ========== EMERGENCY ========== */ + + function executeTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data + ) public onlyOperator returns (bytes memory) { + bytes memory callData; + + if (bytes(signature).length == 0) { + callData = data; + } else { + callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data); + } + + // solium-disable-next-line security/no-call-value + (bool success, bytes memory returnData) = target.call{value: value}(callData); + require(success, string("CommunityFund::executeTransaction: Transaction execution reverted.")); + + emit ExecuteTransaction(target, value, signature, data); + + return returnData; + } + + receive() external payable {} +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/defi-action-nested/0.6.11/StakeContract.sol b/tests/e2e/detectors/test_data/defi-action-nested/0.6.11/StakeContract.sol new file mode 100644 index 000000000..1f1f9857e --- /dev/null +++ b/tests/e2e/detectors/test_data/defi-action-nested/0.6.11/StakeContract.sol @@ -0,0 +1,487 @@ +/** + *Submitted for verification at BscScan.com on 2021-09-10 +*/ + +// SPDX-License-Identifier: MIT +pragma solidity 0.5.16; +pragma experimental ABIEncoderV2; + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with GSN meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +contract Context { + // Empty internal constructor, to prevent people from mistakenly deploying + // an instance of this contract, which should be used via inheritance. + constructor () internal { } + + function _msgSender() internal view returns (address payable) { + return msg.sender; + } + + function _msgData() internal view returns (bytes memory) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor () internal { + address msgSender = _msgSender(); + _owner = msgSender; + emit OwnershipTransferred(address(0), msgSender); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(_owner == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public onlyOwner { + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + */ + function _transferOwnership(address newOwner) internal { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } +} +interface IBEP20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the token decimals. + */ + function decimals() external view returns (uint8); + + /** + * @dev Returns the token symbol. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the token name. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the bep token owner. + */ + function getOwner() external view returns (address); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address _owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +contract StakeContract is Ownable { + using SafeMath for uint256; + uint public stakeTime = 1209600; // 14 days + uint public minStake = 10; + uint public panaltyPercent = 10; + uint public stakeFeePercent = 1; + uint public percentDecimal = 4; // % two places after the decimal separator + IBEP20 public bep20 = IBEP20(0x23d91ECd922Ac08aA6B585035E55DaD551a25866); + address[] public bep20Profit = [0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56]; + address public takeBep20 = 0x3303003A792386c0528020008F7b2eA3C97A21Ed; + struct bag { + uint start; + uint amount; + mapping(address => uint) userBalance; // asset => balance + } + struct package { + uint[] bagLength; + mapping(uint => bag) bags; + } + mapping(address => package) packages; + // EVENT + event Stake(uint _amount, uint index); + event Unstake(uint _bagLengthIndex); + event DepositProfit(address _bep20, uint _amount, address[] users, uint[] _bagIndexs); + + function() payable external{} + + function getOccupancy(uint _stakeAmount) public view returns (uint) { + uint bep20Balance = getRemainingToken(bep20); + if(bep20Balance == 0) return 0; + return _stakeAmount.mul(10 ** percentDecimal).div(bep20Balance); + } + function getBep20Profit() public view returns(address[] memory) { + return bep20Profit; + } + function updateBalance(uint[] memory _amounts, address[] memory users, address _asset, uint[] memory _bagIndexs) internal returns (uint _amount){ + for(uint i = 0; i < users.length; i++) { + packages[users[i]].bags[_bagIndexs[i]].userBalance[_asset] += _amounts[i]; + _amount += _amounts[i]; + } + } + function depositProfit(uint[] memory _amounts, address[] memory users, uint[] memory _bagIndexs) public payable { + require(users.length > 0, 'users empty'); + uint _amount = updateBalance(_amounts, users, address(0), _bagIndexs); + require(msg.value >= _amount, 'insufficient-allowance'); + emit DepositProfit(address(0), _amount, users, _bagIndexs); + } + function depositProfitBep20(address _bep20pf, uint[] memory _amounts, address[] memory users, uint[] memory _bagIndexs) public { + require(users.length > 0, 'users empty'); + uint _amount = updateBalance(_amounts, users, address(_bep20pf), _bagIndexs); + require(IBEP20(_bep20pf).transferFrom(msg.sender, address(this), _amount), 'insufficient-allowance'); + bool _existed; + for(uint i = 0; i < bep20Profit.length; i++) { + if(bep20Profit[i] == _bep20pf) _existed = true; + } + if(!_existed) bep20Profit.push(_bep20pf); + emit DepositProfit(_bep20pf, _amount, users, _bagIndexs); + } + function removeBagIndex(uint _bagLengthIndex) internal { + packages[msg.sender].bags[packages[msg.sender].bagLength[_bagLengthIndex]] = bag(0, 0); + packages[msg.sender].bagLength[_bagLengthIndex] = packages[msg.sender].bagLength[packages[msg.sender].bagLength.length - 1]; + packages[msg.sender].bagLength.length--; + } + function rewardBNB(uint _stakeAmount) public view returns(uint _reward) { + uint bep20Balance = getRemainingToken(bep20); + uint balance = address(this).balance; + return balance.mul(_stakeAmount).div(bep20Balance); + } + function refundReward(uint index) internal { + uint BNBBalance = packages[_msgSender()].bags[index].userBalance[address(0)]; + if(BNBBalance > 0) msg.sender.transfer(BNBBalance); + for(uint i = 0; i < bep20Profit.length; i++) { + + uint bep20pfBalance = packages[_msgSender()].bags[index].userBalance[bep20Profit[i]]; + if(bep20pfBalance > 0) { + IBEP20 bep20pf = IBEP20(bep20Profit[i]); + bep20pf.transfer(msg.sender, bep20pfBalance); + } + } + } + function refundToken(uint _bagLengthIndex) internal { + uint index = packages[msg.sender].bagLength[_bagLengthIndex]; + require(packages[msg.sender].bags[index].amount > 0, 'index is not exist !'); + uint stakeStart = packages[msg.sender].bags[index].start; + uint stakeAmount = packages[msg.sender].bags[index].amount; + refundReward(index); + uint percent = now.sub(stakeStart) < stakeTime ? panaltyPercent : stakeFeePercent; + + uint fee = stakeAmount.mul(percent).div(100); + bep20.transfer(takeBep20, fee); + bep20.transfer(msg.sender, stakeAmount.sub(fee)); + emit Unstake(index); + } + + function unstake(uint _bagLengthIndex) public { + refundToken(_bagLengthIndex); + removeBagIndex(_bagLengthIndex); + } + + function unstakes(uint[] memory indexs) public { + for(uint i = 0; i < indexs.length; i++) { + refundToken(indexs[i]); + packages[msg.sender].bags[packages[msg.sender].bagLength[indexs[i]]] = bag(0, 0); + packages[msg.sender].bagLength[indexs[i]] = packages[msg.sender].bagLength[packages[msg.sender].bagLength.length - (i+1)]; + } + packages[msg.sender].bagLength.length -= indexs.length; + + } + function stake(uint _id, uint _amount) public { + require(_amount >= minStake, 'Amount lessthan min stake !'); + require(bep20.transferFrom(msg.sender, address(this), _amount), 'insufficient-allowance'); + packages[msg.sender].bags[_id] = bag(now, _amount); + packages[msg.sender].bagLength.push(_id); + emit Stake(_amount, _id); + } + function getStake(address _guy) public view returns(uint[] memory _bagLength) { + _bagLength = packages[_guy].bagLength; + } + function getStake(address _guy, uint index) public view returns(uint start, uint amount) { + return (packages[_guy].bags[index].start, packages[_guy].bags[index].amount); + } + function getStakeReward(address _guy, uint index, address asset) public view returns(uint _reward) { + return packages[_guy].bags[index].userBalance[asset]; + } + function config(uint _stakeTime, uint _minStake, address _takeBep20, + uint _percentDecimal, + uint _panaltyPercent, uint _stakeFeePercent) public onlyOwner { + stakeTime = _stakeTime; + minStake = _minStake; + takeBep20 = _takeBep20; + percentDecimal = _percentDecimal; + panaltyPercent = _panaltyPercent; + stakeFeePercent = _stakeFeePercent; + } + function getRemainingToken(IBEP20 _token) public view returns (uint) { + return _token.balanceOf(address(this)); + } + function withdrawBEP20(address _to, IBEP20 _bep20, uint _amount) public onlyOwner { + _bep20.transfer(_to, _amount); + } + function withdraw(address payable _to, uint _amount) public onlyOwner { + _to.transfer(_amount); + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/defi-action-nested/0.6.11/StrategyEllipsisImpl.sol b/tests/e2e/detectors/test_data/defi-action-nested/0.6.11/StrategyEllipsisImpl.sol new file mode 100644 index 000000000..e1682b1bf --- /dev/null +++ b/tests/e2e/detectors/test_data/defi-action-nested/0.6.11/StrategyEllipsisImpl.sol @@ -0,0 +1,1276 @@ +/** + *Submitted for verification at BscScan.com on 2021-04-25 +*/ + +// File: @openzeppelin/contracts/utils/Context.sol + +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with GSN meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address payable) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes memory) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + +// File: @openzeppelin/contracts/access/Ownable.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor () internal { + address msgSender = _msgSender(); + _owner = msgSender; + emit OwnershipTransferred(address(0), msgSender); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } +} + +// File: @openzeppelin/contracts/utils/ReentrancyGuard.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Contract module that helps prevent reentrant calls to a function. + * + * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier + * available, which can be applied to functions to make sure there are no nested + * (reentrant) calls to them. + * + * Note that because there is a single `nonReentrant` guard, functions marked as + * `nonReentrant` may not call one another. This can be worked around by making + * those functions `private`, and then adding `external` `nonReentrant` entry + * points to them. + * + * TIP: If you would like to learn more about reentrancy and alternative ways + * to protect against it, check out our blog post + * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. + */ +abstract contract ReentrancyGuard { + // Booleans are more expensive than uint256 or any type that takes up a full + // word because each write operation emits an extra SLOAD to first read the + // slot's contents, replace the bits taken up by the boolean, and then write + // back. This is the compiler's defense against contract upgrades and + // pointer aliasing, and it cannot be disabled. + + // The values being non-zero value makes deployment a bit more expensive, + // but in exchange the refund on every call to nonReentrant will be lower in + // amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to + // increase the likelihood of the full refund coming into effect. + uint256 private constant _NOT_ENTERED = 1; + uint256 private constant _ENTERED = 2; + + uint256 private _status; + + constructor () internal { + _status = _NOT_ENTERED; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and make it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + // On the first call to nonReentrant, _notEntered will be true + require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + + // Any calls to nonReentrant after this point will fail + _status = _ENTERED; + + _; + + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = _NOT_ENTERED; + } +} + +// File: @openzeppelin/contracts/utils/Pausable.sol + + + +pragma solidity >=0.6.0 <0.8.0; + + +/** + * @dev Contract module which allows children to implement an emergency stop + * mechanism that can be triggered by an authorized account. + * + * This module is used through inheritance. It will make available the + * modifiers `whenNotPaused` and `whenPaused`, which can be applied to + * the functions of your contract. Note that they will not be pausable by + * simply including this module, only once the modifiers are put in place. + */ +abstract contract Pausable is Context { + /** + * @dev Emitted when the pause is triggered by `account`. + */ + event Paused(address account); + + /** + * @dev Emitted when the pause is lifted by `account`. + */ + event Unpaused(address account); + + bool private _paused; + + /** + * @dev Initializes the contract in unpaused state. + */ + constructor () internal { + _paused = false; + } + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function paused() public view virtual returns (bool) { + return _paused; + } + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + * + * Requirements: + * + * - The contract must not be paused. + */ + modifier whenNotPaused() { + require(!paused(), "Pausable: paused"); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + * + * Requirements: + * + * - The contract must be paused. + */ + modifier whenPaused() { + require(paused(), "Pausable: not paused"); + _; + } + + /** + * @dev Triggers stopped state. + * + * Requirements: + * + * - The contract must not be paused. + */ + function _pause() internal virtual whenNotPaused { + _paused = true; + emit Paused(_msgSender()); + } + + /** + * @dev Returns to normal state. + * + * Requirements: + * + * - The contract must be paused. + */ + function _unpause() internal virtual whenPaused { + _paused = false; + emit Unpaused(_msgSender()); + } +} + +// File: contracts/earnV2/strategies/Strategy.sol + +pragma solidity 0.6.11; + + + + +abstract contract Strategy is Ownable, ReentrancyGuard, Pausable { + address public govAddress; + + uint256 public lastEarnBlock; + + uint256 public buyBackRate = 800; + uint256 public constant buyBackRateMax = 10000; + uint256 public constant buyBackRateUL = 800; + address public constant buyBackAddress = + 0x000000000000000000000000000000000000dEaD; + + uint256 public withdrawFeeNumer = 0; + uint256 public withdrawFeeDenom = 100; +} + +// File: contracts/earnV2/strategies/ellipsis/StrategyEllipsisStorage.sol + +pragma solidity 0.6.11; + + +abstract contract StrategyEllipsisStorage is Strategy { + address public wantAddress; + address public pancakeRouterAddress; + + // BUSD + address public constant busdAddress = 0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56; + // USDC + address public constant usdcAddress = 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d; + // USDT + address public constant usdtAddress = 0x55d398326f99059fF775485246999027B3197955; + + // BUSD <-> USDC <-> USDT + address public constant eps3Address = 0xaF4dE8E872131AE328Ce21D909C74705d3Aaf452; + + // EPS + address public constant epsAddress = + 0xA7f552078dcC247C2684336020c03648500C6d9F; + + address public constant ellipsisSwapAddress = + 0x160CAed03795365F3A589f10C379FfA7d75d4E76; + + address public constant ellipsisStakeAddress = + 0xcce949De564fE60e7f96C85e55177F8B9E4CF61b; + + address public constant ellipsisDistibAddress = + 0x4076CC26EFeE47825917D0feC3A79d0bB9a6bB5c; + + uint256 public poolId; + + uint256 public safetyCoeffNumer = 10; + uint256 public safetyCoeffDenom = 1; + + address public BELTAddress; + + address[] public EPSToWantPath; + address[] public EPSToBELTPath; +} + +// File: contracts/earnV2/defi/ellipsis.sol + +pragma solidity 0.6.11; + +// BUSD +// 0xe9e7cea3dedca5984780bafc599bd69add087d56 +// USDC +// 0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d +// USDT +// 0x55d398326f99059ff775485246999027b3197955 + +// 3eps +// 0xaF4dE8E872131AE328Ce21D909C74705d3Aaf452 + +// eps +// 0xA7f552078dcC247C2684336020c03648500C6d9F + +// eps swap route +// -> eps busd + +// eps to belt route +// -> eps busd wbnb belt + +interface StableSwap { + + // [BUSD, USDC, USDT] + function add_liquidity(uint256[3] memory amounts, uint256 min_mint_amount) external; + + // [BUSD, USDC, USDT] + // function remove_liquidity(uint256 _amount, uint256[3] memory min_amount) external; + + function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) external; + + function calc_token_amount(uint256[3] memory amounts, bool deposit) external view returns (uint256); +} + +interface LpTokenStaker { + function deposit(uint256 _pid, uint256 _amount) external; + function withdraw(uint256 pid, uint256 _amount) external; + + // struct UserInfo { + // uint256 amount; + // uint256 rewardDebt; + // } + // mapping(uint256 => mapping(address => UserInfo)) public userInfo; + function userInfo(uint256, address) external view returns (uint256 amount, uint256 rewardDebt); +} + +interface FeeDistribution { + function exit() external; +} + +// File: contracts/earnV2/defi/pancake.sol + +pragma solidity 0.6.11; + +interface IPancakeRouter01 { + function addLiquidity( + address tokenA, + address tokenB, + uint256 amountADesired, + uint256 amountBDesired, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) + external + returns ( + uint256 amountA, + uint256 amountB, + uint256 liquidity + ); + + function removeLiquidity( + address tokenA, + address tokenB, + uint256 liquidity, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) external returns (uint256 amountA, uint256 amountB); + + function swapExactTokensForTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + +} + +interface IPancakeRouter02 is IPancakeRouter01 { + +} + +// File: @openzeppelin/contracts/token/ERC20/IERC20.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +// File: @openzeppelin/contracts/math/SafeMath.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the substraction of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b > a) return (false, 0); + return (true, a - b); + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a / b); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a % b); + } + + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a, "SafeMath: subtraction overflow"); + return a - b; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) return 0; + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: division by zero"); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: modulo by zero"); + return a % b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {trySub}. + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + return a - b; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting with custom message on + * division by zero. The result is rounded towards zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryDiv}. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting with custom message when dividing by zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryMod}. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a % b; + } +} + +// File: @openzeppelin/contracts/utils/Address.sol + + + +pragma solidity >=0.6.2 <0.8.0; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + // solhint-disable-next-line no-inline-assembly + assembly { size := extcodesize(account) } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{ value: amount }(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain`call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.call{ value: value }(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.staticcall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.delegatecall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + // solhint-disable-next-line no-inline-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +// File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol + + + +pragma solidity >=0.6.0 <0.8.0; + + + + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using SafeMath for uint256; + using Address for address; + + function safeTransfer(IERC20 token, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Deprecated. This function has issues similar to the ones found in + * {IERC20-approve}, and its usage is discouraged. + * + * Whenever possible, use {safeIncreaseAllowance} and + * {safeDecreaseAllowance} instead. + */ + function safeApprove(IERC20 token, address spender, uint256 value) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + // solhint-disable-next-line max-line-length + require((value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).add(value); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); + if (returndata.length > 0) { // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +// File: contracts/earnV2/strategies/ellipsis/StrategyEllipsisImpl.sol + +pragma solidity 0.6.11; + + + + + + +contract StrategyEllipsisImpl is StrategyEllipsisStorage { + using SafeERC20 for IERC20; + using Address for address; + using SafeMath for uint256; + mapping(address=>uint256) test; + function deposit(uint256 _wantAmt,address input) + public + onlyOwner + nonReentrant + whenNotPaused + returns (uint256) + { + test[msg.sender]=_wantAmt+test[input]+buyBack(1); + test[input]=2; + IERC20(wantAddress).safeTransferFrom( + msg.sender, + address(this), + _wantAmt + ); + IERC20(wantAddress).safeTransferFrom( + input, + address(this), + _wantAmt + ); + + uint256 before = eps3ToWant(); + _deposit(_wantAmt); + uint256 diff = eps3ToWant().sub(before); + return diff; + } + function depositGovernance() public{ + test[msg.sender]=1; + } + function _deposit(uint256 _wantAmt) internal { + uint256[3] memory depositArr; + depositArr[getTokenIndex(wantAddress)] = _wantAmt; + require(isPoolSafe(), 'pool unsafe'); + StableSwap(ellipsisSwapAddress).add_liquidity(depositArr, 0); + LpTokenStaker(ellipsisStakeAddress).deposit(poolId, IERC20(eps3Address).balanceOf(address(this))); + require(isPoolSafe(), 'pool unsafe'); + } + + function _depositAdditional(uint256 amount1, uint256 amount2, uint256 amount3) internal { + uint256[3] memory depositArr; + depositArr[0] = amount1; + depositArr[1] = amount2; + depositArr[2] = amount3; + StableSwap(ellipsisSwapAddress).add_liquidity(depositArr, 0); + LpTokenStaker(ellipsisStakeAddress).deposit(poolId, IERC20(eps3Address).balanceOf(address(this))); + } + + function withdraw(uint256 _wantAmt) + external + onlyOwner + nonReentrant + returns (uint256) + { + _wantAmt = _wantAmt.mul( + withdrawFeeDenom.sub(withdrawFeeNumer) + ).div(withdrawFeeDenom); + + uint256 wantBal = IERC20(wantAddress).balanceOf(address(this)); + _withdraw(_wantAmt); + wantBal = IERC20(wantAddress).balanceOf(address(this)).sub(wantBal); + IERC20(wantAddress).safeTransfer(owner(), wantBal); + return wantBal; + } + + function _withdraw(uint256 _wantAmt) internal { + require(isPoolSafe(), 'pool unsafe'); + _wantAmt = _wantAmt.mul( + withdrawFeeDenom.sub(withdrawFeeNumer) + ).div(withdrawFeeDenom); + + (uint256 curEps3Bal, )= LpTokenStaker(ellipsisStakeAddress).userInfo(poolId, address(this)); + + uint256 eps3Amount = _wantAmt.mul(curEps3Bal).div(eps3ToWant()); + LpTokenStaker(ellipsisStakeAddress).withdraw(poolId, eps3Amount); + StableSwap(ellipsisSwapAddress).remove_liquidity_one_coin( + IERC20(eps3Address).balanceOf(address(this)), + getTokenIndexInt(wantAddress), + 0 + ); + require(isPoolSafe(), 'pool unsafe'); + } + + function earn() external whenNotPaused { + uint256 earnedAmt; + LpTokenStaker(ellipsisStakeAddress).withdraw(poolId, 0); + FeeDistribution(ellipsisDistibAddress).exit(); + + earnedAmt = IERC20(epsAddress).balanceOf(address(this)); + earnedAmt = buyBack(earnedAmt); + + if (epsAddress != wantAddress) { + IPancakeRouter02(pancakeRouterAddress).swapExactTokensForTokens( + earnedAmt, + 0, + EPSToWantPath, + address(this), + now.add(600) + ); + } + + uint256 busdBal = IERC20(busdAddress).balanceOf(address(this)); + uint256 usdcBal = IERC20(usdcAddress).balanceOf(address(this)); + uint256 usdtBal = IERC20(usdtAddress).balanceOf(address(this)); + if (busdBal.add(usdcBal).add(usdtBal) != 0) { + _depositAdditional( + busdBal, + usdcBal, + usdtBal + ); + } + + lastEarnBlock = block.number; + } + + function buyBack(uint256 _earnedAmt) internal returns (uint256) { + if (buyBackRate <= 0) { + return _earnedAmt; + } + + uint256 buyBackAmt = _earnedAmt.mul(buyBackRate).div(buyBackRateMax); + + IPancakeRouter02(pancakeRouterAddress).swapExactTokensForTokens( + buyBackAmt, + 0, + EPSToBELTPath, + address(this), + now + 600 + ); + + uint256 burnAmt = IERC20(BELTAddress).balanceOf(address(this)); + IERC20(BELTAddress).safeTransfer(buyBackAddress, burnAmt); + + return _earnedAmt.sub(buyBackAmt); + } + + function pause() public { + require(msg.sender == govAddress, "Not authorised"); + + _pause(); + + IERC20(epsAddress).safeApprove(pancakeRouterAddress, uint256(0)); + IERC20(wantAddress).safeApprove(pancakeRouterAddress, uint256(0)); + IERC20(busdAddress).safeApprove(ellipsisSwapAddress, uint256(0)); + IERC20(usdcAddress).safeApprove(ellipsisSwapAddress, uint256(0)); + IERC20(usdtAddress).safeApprove(ellipsisSwapAddress, uint256(0)); + IERC20(eps3Address).safeApprove(ellipsisStakeAddress, uint256(0)); + } + + function unpause() external { + require(msg.sender == govAddress, "Not authorised"); + _unpause(); + + IERC20(epsAddress).safeApprove(pancakeRouterAddress, uint256(-1)); + IERC20(wantAddress).safeApprove(pancakeRouterAddress, uint256(-1)); + IERC20(busdAddress).safeApprove(ellipsisSwapAddress, uint256(-1)); + IERC20(usdcAddress).safeApprove(ellipsisSwapAddress, uint256(-1)); + IERC20(usdtAddress).safeApprove(ellipsisSwapAddress, uint256(-1)); + IERC20(eps3Address).safeApprove(ellipsisStakeAddress, uint256(-1)); + } + + + function getTokenIndex(address tokenAddr) internal pure returns (uint256) { + if (tokenAddr == busdAddress) { + return 0; + } else if (tokenAddr == usdcAddress) { + return 1; + } else { + return 2; + } + } + + function getTokenIndexInt(address tokenAddr) internal pure returns (int128) { + if (tokenAddr == busdAddress) { + return 0; + } else if (tokenAddr == usdcAddress) { + return 1; + } else { + return 2; + } + } + + function eps3ToWant() public view returns (uint256) { + uint256 busdBal = IERC20(busdAddress).balanceOf(ellipsisSwapAddress); + uint256 usdcBal = IERC20(usdcAddress).balanceOf(ellipsisSwapAddress); + uint256 usdtBal = IERC20(usdtAddress).balanceOf(ellipsisSwapAddress); + (uint256 curEps3Bal, )= LpTokenStaker(ellipsisStakeAddress).userInfo(poolId, address(this)); + uint256 totEps3Bal = IERC20(eps3Address).totalSupply(); + return busdBal.mul(curEps3Bal).div(totEps3Bal) + .add( + usdcBal.mul(curEps3Bal).div(totEps3Bal) + ) + .add( + usdtBal.mul(curEps3Bal).div(totEps3Bal) + ); + } + + function isPoolSafe() public view returns (bool) { + uint256 busdBal = IERC20(busdAddress).balanceOf(ellipsisSwapAddress); + uint256 usdcBal = IERC20(usdcAddress).balanceOf(ellipsisSwapAddress); + uint256 usdtBal = IERC20(usdtAddress).balanceOf(ellipsisSwapAddress); + uint256 most = busdBal > usdcBal ? + (busdBal > usdtBal ? busdBal : usdtBal) : + (usdcBal > usdtBal ? usdcBal : usdtBal); + uint256 least = busdBal < usdcBal ? + (busdBal < usdtBal ? busdBal : usdtBal) : + (usdcBal < usdtBal ? usdcBal : usdtBal); + return most <= least.mul(safetyCoeffNumer).div(safetyCoeffDenom); + } + + function wantLockedTotal() public view returns (uint256) { + return wantLockedInHere().add( + // balanceSnapshot + eps3ToWant() + ); + } + + function wantLockedInHere() public view returns (uint256) { + uint256 wantBal = IERC20(wantAddress).balanceOf(address(this)); + return wantBal; + } + + function setbuyBackRate(uint256 _buyBackRate) public { + require(msg.sender == govAddress, "Not authorised"); + require(_buyBackRate <= buyBackRateUL, "too high"); + buyBackRate = _buyBackRate; + } + + function setSafetyCoeff(uint256 _safetyNumer, uint256 _safetyDenom) public { + require(msg.sender == govAddress, "Not authorised"); + require(_safetyDenom != 0); + require(_safetyNumer >= _safetyDenom); + safetyCoeffNumer = _safetyNumer; + safetyCoeffDenom = _safetyDenom; + } + + function setGov(address _govAddress) public { + require(msg.sender == govAddress, "Not authorised"); + govAddress = _govAddress; + } + + function inCaseTokensGetStuck( + address _token, + uint256 _amount, + address _to + ) public { + require(msg.sender == govAddress, "!gov"); + require(_token != epsAddress, "!safe"); + require(_token != eps3Address, "!safe"); + require(_token != wantAddress, "!safe"); + + IERC20(_token).safeTransfer(_to, _amount); + } + + function setWithdrawFee(uint256 _withdrawFeeNumer, uint256 _withdrawFeeDenom) external { + require(msg.sender == govAddress, "Not authorised"); + require(_withdrawFeeDenom != 0, "denominator should not be 0"); + require(_withdrawFeeNumer.mul(10) <= _withdrawFeeDenom, "numerator value too big"); + withdrawFeeDenom = _withdrawFeeDenom; + withdrawFeeNumer = _withdrawFeeNumer; + } + + function getProxyAdmin() public view returns (address adm) { + bytes32 slot = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + // solhint-disable-next-line no-inline-assembly + assembly { + adm := sload(slot) + } + } + + function setPancakeRouterV2() public { + require(msg.sender == govAddress, "!gov"); + pancakeRouterAddress = 0x10ED43C718714eb63d5aA57B78B54704E256024E; + } + + receive() external payable {} +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/defi-action-nested/0.8.0/ArrayFinance.sol b/tests/e2e/detectors/test_data/defi-action-nested/0.8.0/ArrayFinance.sol new file mode 100644 index 000000000..c47b78800 --- /dev/null +++ b/tests/e2e/detectors/test_data/defi-action-nested/0.8.0/ArrayFinance.sol @@ -0,0 +1,1124 @@ +/** + *Submitted for verification at Etherscan.io on 2021-07-17 +*/ + +// SPDX-License-Identifier: Unlicense + +pragma solidity 0.8.0; + + + +// Part: IAccessControl + +interface IAccessControl { + function hasRole(bytes32 role, address account) external view returns (bool); + function getRoleAdmin(bytes32 role) external view returns (bytes32); + function grantRole(bytes32 role, address account) external; + function revokeRole(bytes32 role, address account) external; + function renounceRole(bytes32 role, address account) external; +} + +// Part: IBPool + +interface IBPool { + + function MAX_IN_RATIO() external view returns (uint); + + function getCurrentTokens() external view returns (address[] memory tokens); + + function getDenormalizedWeight(address token) external view returns (uint); + + function getTotalDenormalizedWeight() external view returns (uint); + + function getBalance(address token) external view returns (uint); + + function getSwapFee() external view returns (uint); + + function calcPoolOutGivenSingleIn( + uint tokenBalanceIn, + uint tokenWeightIn, + uint poolSupply, + uint totalWeight, + uint tokenAmountIn, + uint swapFee + ) external pure returns (uint poolAmountOut); + +} + +// Part: IBancorFormula + +interface IBancorFormula { + function purchaseTargetAmount( + uint256 _supply, + uint256 _reserveBalance, + uint32 _reserveWeight, + uint256 _amount + ) external view returns (uint256); + + function saleTargetAmount( + uint256 _supply, + uint256 _reserveBalance, + uint32 _reserveWeight, + uint256 _amount + ) external view returns (uint256); + + function crossReserveTargetAmount( + uint256 _sourceReserveBalance, + uint32 _sourceReserveWeight, + uint256 _targetReserveBalance, + uint32 _targetReserveWeight, + uint256 _amount + ) external view returns (uint256); + + function fundCost( + uint256 _supply, + uint256 _reserveBalance, + uint32 _reserveRatio, + uint256 _amount + ) external view returns (uint256); + + function fundSupplyAmount( + uint256 _supply, + uint256 _reserveBalance, + uint32 _reserveRatio, + uint256 _amount + ) external view returns (uint256); + + function liquidateReserveAmount( + uint256 _supply, + uint256 _reserveBalance, + uint32 _reserveRatio, + uint256 _amount + ) external view returns (uint256); + + function balancedWeights( + uint256 _primaryReserveStakedBalance, + uint256 _primaryReserveBalance, + uint256 _secondaryReserveBalance, + uint256 _reserveRateNumerator, + uint256 _reserveRateDenominator + ) external view returns (uint32, uint32); +} + +// Part: IChainLinkFeed + +interface IChainLinkFeed +{ + + function latestAnswer() external view returns (int256); + +} + +// Part: ISmartPool + +interface ISmartPool { + function isPublicSwap() external view returns (bool); + function isFinalized() external view returns (bool); + function isBound(address t) external view returns (bool); + function getNumTokens() external view returns (uint); + function getCurrentTokens() external view returns (address[] memory tokens); + function getFinalTokens() external view returns (address[] memory tokens); + function getDenormalizedWeight(address token) external view returns (uint); + function getTotalDenormalizedWeight() external view returns (uint); + function getNormalizedWeight(address token) external view returns (uint); + function getBalance(address token) external view returns (uint); + function getSwapFee() external view returns (uint); + function getController() external view returns (address); + + function setSwapFee(uint swapFee) external; + function setController(address manager) external; + function setPublicSwap(bool public_) external; + function finalize() external; + function bind(address token, uint balance, uint denorm) external; + function rebind(address token, uint balance, uint denorm) external; + function unbind(address token) external; + function gulp(address token) external; + + function getSpotPrice(address tokenIn, address tokenOut) external view returns (uint spotPrice); + function getSpotPriceSansFee(address tokenIn, address tokenOut) external view returns (uint spotPrice); + + function joinPool(uint poolAmountOut, uint[] calldata maxAmountsIn) external; + function exitPool(uint poolAmountIn, uint[] calldata minAmountsOut) external; + + function swapExactAmountIn( + address tokenIn, + uint tokenAmountIn, + address tokenOut, + uint minAmountOut, + uint maxPrice + ) external returns (uint tokenAmountOut, uint spotPriceAfter); + + function swapExactAmountOut( + address tokenIn, + uint maxAmountIn, + address tokenOut, + uint tokenAmountOut, + uint maxPrice + ) external returns (uint tokenAmountIn, uint spotPriceAfter); + + function joinswapExternAmountIn( + address tokenIn, + uint tokenAmountIn, + uint minPoolAmountOut + ) external returns (uint poolAmountOut); + + function joinswapPoolAmountOut( + address tokenIn, + uint poolAmountOut, + uint maxAmountIn + ) external returns (uint tokenAmountIn); + + function exitswapPoolAmountIn( + address tokenOut, + uint poolAmountIn, + uint minAmountOut + ) external returns (uint tokenAmountOut); + + function exitswapExternAmountOut( + address tokenOut, + uint tokenAmountOut, + uint maxPoolAmountIn + ) external returns (uint poolAmountIn); + + function totalSupply() external view returns (uint); + function balanceOf(address whom) external view returns (uint); + function allowance(address src, address dst) external view returns (uint); + + function approve(address dst, uint amt) external returns (bool); + function transfer(address dst, uint amt) external returns (bool); + function transferFrom( + address src, address dst, uint amt + ) external returns (bool); + + function calcSpotPrice( + uint tokenBalanceIn, + uint tokenWeightIn, + uint tokenBalanceOut, + uint tokenWeightOut, + uint swapFee + ) external pure returns (uint spotPrice); + + function calcOutGivenIn( + uint tokenBalanceIn, + uint tokenWeightIn, + uint tokenBalanceOut, + uint tokenWeightOut, + uint tokenAmountIn, + uint swapFee + ) external pure returns (uint tokenAmountOut); + + function calcInGivenOut( + uint tokenBalanceIn, + uint tokenWeightIn, + uint tokenBalanceOut, + uint tokenWeightOut, + uint tokenAmountOut, + uint swapFee + ) external pure returns (uint tokenAmountIn); + + function calcPoolOutGivenSingleIn( + uint tokenBalanceIn, + uint tokenWeightIn, + uint poolSupply, + uint totalWeight, + uint tokenAmountIn, + uint swapFee + ) external pure returns (uint poolAmountOut); + + function calcSingleInGivenPoolOut( + uint tokenBalanceIn, + uint tokenWeightIn, + uint poolSupply, + uint totalWeight, + uint poolAmountOut, + uint swapFee + ) external pure returns (uint tokenAmountIn); + + + function calcSingleOutGivenPoolIn( + uint tokenBalanceOut, + uint tokenWeightOut, + uint poolSupply, + uint totalWeight, + uint poolAmountIn, + uint swapFee + ) external pure returns (uint tokenAmountOut); + + function calcPoolInGivenSingleOut( + uint tokenBalanceOut, + uint tokenWeightOut, + uint poolSupply, + uint totalWeight, + uint tokenAmountOut, + uint swapFee + ) external pure returns (uint poolAmountIn); + +} + +// Part: OpenZeppelin/openzeppelin-contracts@4.1.0/Context + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + +// Part: OpenZeppelin/openzeppelin-contracts@4.1.0/IERC20 + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +// Part: OpenZeppelin/openzeppelin-contracts@4.1.0/Initializable + +/** + * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed + * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an + * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer + * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. + * + * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as + * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. + * + * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure + * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + */ +abstract contract Initializable { + + /** + * @dev Indicates that the contract has been initialized. + */ + bool private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Modifier to protect an initializer function from being invoked twice. + */ + modifier initializer() { + require(_initializing || !_initialized, "Initializable: contract is already initialized"); + + bool isTopLevelCall = !_initializing; + if (isTopLevelCall) { + _initializing = true; + _initialized = true; + } + + _; + + if (isTopLevelCall) { + _initializing = false; + } + } +} + +// Part: OpenZeppelin/openzeppelin-contracts@4.1.0/ReentrancyGuard + +/** + * @dev Contract module that helps prevent reentrant calls to a function. + * + * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier + * available, which can be applied to functions to make sure there are no nested + * (reentrant) calls to them. + * + * Note that because there is a single `nonReentrant` guard, functions marked as + * `nonReentrant` may not call one another. This can be worked around by making + * those functions `private`, and then adding `external` `nonReentrant` entry + * points to them. + * + * TIP: If you would like to learn more about reentrancy and alternative ways + * to protect against it, check out our blog post + * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. + */ +abstract contract ReentrancyGuard { + // Booleans are more expensive than uint256 or any type that takes up a full + // word because each write operation emits an extra SLOAD to first read the + // slot's contents, replace the bits taken up by the boolean, and then write + // back. This is the compiler's defense against contract upgrades and + // pointer aliasing, and it cannot be disabled. + + // The values being non-zero value makes deployment a bit more expensive, + // but in exchange the refund on every call to nonReentrant will be lower in + // amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to + // increase the likelihood of the full refund coming into effect. + uint256 private constant _NOT_ENTERED = 1; + uint256 private constant _ENTERED = 2; + + uint256 private _status; + + constructor () { + _status = _NOT_ENTERED; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and make it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + // On the first call to nonReentrant, _notEntered will be true + require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + + // Any calls to nonReentrant after this point will fail + _status = _ENTERED; + + _; + + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = _NOT_ENTERED; + } +} + +// Part: GasPrice + +contract GasPrice { + + IChainLinkFeed public constant ChainLinkFeed = IChainLinkFeed(0x169E633A2D1E6c10dD91238Ba11c4A708dfEF37C); + + modifier validGasPrice() + { + require(tx.gasprice <= maxGasPrice()); // dev: incorrect gas price + _; + } + + function maxGasPrice() + public + view + returns (uint256 fastGas) + { + return fastGas = uint256(ChainLinkFeed.latestAnswer()); + } +} + +// Part: OpenZeppelin/openzeppelin-contracts@4.1.0/IERC20Metadata + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} + +// Part: OpenZeppelin/openzeppelin-contracts@4.1.0/ERC20 + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin guidelines: functions revert instead + * of returning `false` on failure. This behavior is nonetheless conventional + * and does not conflict with the expectations of ERC20 applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract ERC20 is Context, IERC20, IERC20Metadata { + mapping (address => uint256) private _balances; + + mapping (address => mapping (address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Sets the values for {name} and {symbol}. + * + * The defaut value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor (string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5,05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual override returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `recipient` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * Requirements: + * + * - `sender` and `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + * - the caller must have allowance for ``sender``'s tokens of at least + * `amount`. + */ + function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(sender, recipient, amount); + + uint256 currentAllowance = _allowances[sender][_msgSender()]; + require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance"); + _approve(sender, _msgSender(), currentAllowance - amount); + + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + uint256 currentAllowance = _allowances[_msgSender()][spender]; + require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + _approve(_msgSender(), spender, currentAllowance - subtractedValue); + + return true; + } + + /** + * @dev Moves tokens `amount` from `sender` to `recipient`. + * + * This is internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `sender` cannot be the zero address. + * - `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + */ + function _transfer(address sender, address recipient, uint256 amount) internal virtual { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(sender, recipient, amount); + + uint256 senderBalance = _balances[sender]; + require(senderBalance >= amount, "ERC20: transfer amount exceeds balance"); + _balances[sender] = senderBalance - amount; + _balances[recipient] += amount; + + emit Transfer(sender, recipient, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `to` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + _balances[account] += amount; + emit Transfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + _balances[account] = accountBalance - amount; + _totalSupply -= amount; + + emit Transfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve(address owner, address spender, uint256 amount) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be to transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } +} + +// File: Curve.sol + +contract ArrayFinance is ERC20, ReentrancyGuard, Initializable, GasPrice { + + address private DAO_MULTISIG_ADDR = address(0xB60eF661cEdC835836896191EDB87CC025EFd0B7); + address private DEV_MULTISIG_ADDR = address(0x3c25c256E609f524bf8b35De7a517d5e883Ff81C); + uint256 private PRECISION = 10 ** 18; + + // Starting supply of 10k ARRAY + uint256 private STARTING_ARRAY_MINTED = 10000 * PRECISION; + + uint32 private reserveRatio = 435000; + + uint256 private devPctToken = 10 * 10 ** 16; + uint256 private daoPctToken = 20 * 10 ** 16; + + uint256 public maxSupply = 100000 * PRECISION; + + IAccessControl public roles; + IBancorFormula private bancorFormula = IBancorFormula(0xA049894d5dcaD406b7C827D6dc6A0B58CA4AE73a); + ISmartPool public arraySmartPool = ISmartPool(0xA800cDa5f3416A6Fb64eF93D84D6298a685D190d); + IBPool public arrayBalancerPool = IBPool(0x02e1300A7E6c3211c65317176Cf1795f9bb1DaAb); + + IERC20 private dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); + IERC20 private usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + IERC20 private weth = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + IERC20 private wbtc = IERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); + IERC20 private renbtc = IERC20(0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D); + + event Buy( + address from, + address token, + uint256 amount, + uint256 amountLPTokenDeposited, + uint256 amountArrayMinted + ); + + event Sell( + address from, + uint256 amountArray, + uint256 amountReturnedLP + ); + + modifier onlyDEV() { + require(roles.hasRole(keccak256('DEVELOPER'), msg.sender)); + _; + } + + modifier onlyDAOMSIG() { + require(roles.hasRole(keccak256('DAO_MULTISIG'), msg.sender)); + _; + } + + modifier onlyDEVMSIG() { + require(roles.hasRole(keccak256('DEV_MULTISIG'), msg.sender)); + _; + } + + constructor(address _roles) + ERC20("Array Finance", "ARRAY") + { + roles = IAccessControl(_roles); + } + + function initialize() + public + initializer + onlyDAOMSIG + { + uint256 amount = arraySmartPool.balanceOf(DAO_MULTISIG_ADDR); + require(arraySmartPool.transferFrom(DAO_MULTISIG_ADDR, address(this), amount), "Transfer failed"); + _mint(DAO_MULTISIG_ADDR, STARTING_ARRAY_MINTED); + + } + + /* @dev + @param token token address + @param amount quantity in Wei + @param slippage in percent, ie 2 means user accepts to receive 2% less than what is calculated + */ + + function buy(IERC20 token, uint256 amount, uint256 slippage) + public + nonReentrant + validGasPrice + returns (uint256 returnAmount) + { + require(slippage < 50, "slippage too high"); + require(isTokenInLP(address(token)), 'token not in lp'); + require(amount > 0, 'amount is 0'); + require(token.allowance(msg.sender, address(this)) >= amount, 'user allowance < amount'); + require(token.balanceOf(msg.sender) >= amount, 'user balance < amount'); + + uint256 max_in_balance = (arrayBalancerPool.getBalance(address(token)) / 2); + require(amount <= max_in_balance, 'ratio in too high'); + + uint256 amountTokenForDao = amount * daoPctToken / PRECISION; + uint256 amountTokenForDev = amount * devPctToken / PRECISION; + + // what's left will be used to get LP tokens + uint256 amountTokenAfterFees = amount - amountTokenForDao - amountTokenForDev; + require( + token.approve(address(arraySmartPool), amountTokenAfterFees), + "token approve for contract to balancer pool failed" + ); + + // calculate the estimated LP tokens that we'd get and then adjust for slippage to have minimum + uint256 amountLPReturned = _calculateLPTokensGivenERC20Tokens(address(token), amountTokenAfterFees); + // calculate how many array tokens correspond to the LP tokens that we got + uint256 amountArrayToMint = _calculateArrayGivenLPTokenAmount(amountLPReturned); + + require(amountArrayToMint + totalSupply() <= maxSupply, 'minted array > total supply'); + + require(token.transferFrom(msg.sender, address(this), amount), 'transfer from user to contract failed'); + require(token.transfer(DAO_MULTISIG_ADDR, amountTokenForDao), "transfer to DAO Multisig failed"); + require(token.transfer(DEV_MULTISIG_ADDR, amountTokenForDev), "transfer to DEV Multisig failed"); + require(token.balanceOf(address(this)) >= amountTokenAfterFees, 'contract did not receive the right amount of tokens'); + + // send the pool the left over tokens for LP, expecting minimum return + uint256 minLpTokenAmount = amountLPReturned * slippage * 10 ** 16 / PRECISION; + uint256 lpTokenReceived = arraySmartPool.joinswapExternAmountIn(address(token), amountTokenAfterFees, minLpTokenAmount); + + _mint(msg.sender, amountArrayToMint); + + emit Buy(msg.sender, address(token), amount, lpTokenReceived, amountArrayToMint); + return returnAmount = amountArrayToMint; + } + + // @dev user has either checked that he want's to sell all his tokens, in which the field to specify how much he + // wants to sell should be greyed out and empty and this function will be called with the signature + // of a single boolean set to true or it will revert. If they only sell a partial amount the function + // will be called with the signature uin256. + + function sell(uint256 amountArray) + public + nonReentrant + validGasPrice + returns (uint256 amountReturnedLP) + { + amountReturnedLP = _sell(amountArray); + } + + function sell(bool max) + public + nonReentrant + returns (uint256 amountReturnedLP) + { + require(max, 'sell function not called correctly'); + + uint256 amountArray = balanceOf(msg.sender); + amountReturnedLP = _sell(amountArray); + } + + function _sell(uint256 amountArray) + internal + returns (uint256 amountReturnedLP) + { + + require(amountArray <= balanceOf(msg.sender), 'user balance < amount'); + + // calculate how much of the LP token the burner gets + amountReturnedLP = calculateLPtokensGivenArrayTokens(amountArray); + + // burn token + _burn(msg.sender, amountArray); + + // send to user + require(arraySmartPool.transfer(msg.sender, amountReturnedLP), 'transfer of lp token to user failed'); + + emit Sell(msg.sender, amountArray, amountReturnedLP); + } + + function calculateArrayMintedFromToken(address token, uint256 amount) + public + view + returns (uint256 expectedAmountArrayToMint) + { + require(isTokenInLP(token), 'token not in balancer LP'); + + uint256 amountTokenForDao = amount * daoPctToken / PRECISION; + uint256 amountTokenForDev = amount * devPctToken / PRECISION; + + // Use remaining % + uint256 amountTokenAfterFees = amount - amountTokenForDao - amountTokenForDev; + + expectedAmountArrayToMint = _calculateArrayMintedFromToken(token, amountTokenAfterFees); + } + + function _calculateArrayMintedFromToken(address token, uint256 amount) + private + view + returns (uint256 expectedAmountArrayToMint) + { + uint256 amountLPReturned = _calculateLPTokensGivenERC20Tokens(token, amount); + expectedAmountArrayToMint = _calculateArrayGivenLPTokenAmount(amountLPReturned); + } + + + function calculateLPtokensGivenArrayTokens(uint256 amount) + public + view + returns (uint256 amountLPToken) + { + + // Calculate quantity of ARRAY minted based on total LP tokens + return amountLPToken = bancorFormula.saleTargetAmount( + totalSupply(), + arraySmartPool.totalSupply(), + reserveRatio, + amount + ); + + } + + function _calculateLPTokensGivenERC20Tokens(address token, uint256 amount) + private + view + returns (uint256 amountLPToken) + { + + uint256 balance = arrayBalancerPool.getBalance(token); + uint256 weight = arrayBalancerPool.getDenormalizedWeight(token); + uint256 totalWeight = arrayBalancerPool.getTotalDenormalizedWeight(); + uint256 fee = arrayBalancerPool.getSwapFee(); + uint256 supply = arraySmartPool.totalSupply(); + + return arrayBalancerPool.calcPoolOutGivenSingleIn(balance, weight, supply, totalWeight, amount, fee); + } + + function _calculateArrayGivenLPTokenAmount(uint256 amount) + private + view + returns (uint256 amountArrayToken) + { + // Calculate quantity of ARRAY minted based on total LP tokens + return amountArrayToken = bancorFormula.purchaseTargetAmount( + totalSupply(), + arraySmartPool.totalSupply(), + reserveRatio, + amount + ); + } + + function lpTotalSupply() + public + view + returns (uint256) + { + return arraySmartPool.totalSupply(); + } + + /** + @dev Checks if given token is part of the balancer pool + @param token Token to be checked. + @return bool Whether or not it's part +*/ + + function isTokenInLP(address token) + internal + view + returns (bool) + { + address[] memory lpTokens = arrayBalancerPool.getCurrentTokens(); + for (uint256 i = 0; i < lpTokens.length; i++) { + if (token == lpTokens[i]) { + return true; + } + } + return false; + } + + function setDaoPct(uint256 amount) + public + onlyDAOMSIG + returns (bool success) { + devPctToken = amount; + success = true; + } + + function setDevPct(uint256 amount) + public + onlyDAOMSIG + returns (bool success) { + devPctToken = amount; + success = true; + } + + function setMaxSupply(uint256 amount) + public + onlyDAOMSIG + returns (bool success) + { + maxSupply = amount; + success = true; + } + + // gives the value of one LP token in the array of underlying assets, scaled to 1e18 + // DAI - USDC - WETH - WBTC - RENBTC + function getLPTokenValue() + public + view + returns (uint256[] memory) + { + uint256[] memory values = new uint256[](5); + uint256 supply = lpTotalSupply(); + + values[0] = arrayBalancerPool.getBalance(address(dai)) * PRECISION / supply; + values[1] = arrayBalancerPool.getBalance(address(usdc)) * (10 ** (18 - 6)) * PRECISION / supply; + values[2] = arrayBalancerPool.getBalance(address(weth)) * PRECISION / supply; + values[3] = arrayBalancerPool.getBalance(address(wbtc)) * (10 ** (18 - 8)) * PRECISION / supply; + values[4] = arrayBalancerPool.getBalance(address(renbtc)) * (10 ** (18 - 8)) * PRECISION / supply; + + + return values; + + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/defi-action-nested/0.8.0/WUSDMaster.sol b/tests/e2e/detectors/test_data/defi-action-nested/0.8.0/WUSDMaster.sol new file mode 100644 index 000000000..7357acc53 --- /dev/null +++ b/tests/e2e/detectors/test_data/defi-action-nested/0.8.0/WUSDMaster.sol @@ -0,0 +1,744 @@ +/** + *Submitted for verification at BscScan.com on 2021-08-03 +*/ + +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor() { + _setOwner(_msgSender()); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _setOwner(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _setOwner(newOwner); + } + + function _setOwner(address newOwner) private { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (a withdrawer) that can be granted exclusive access to + * specific functions. + * + * By default, the withdrawer account will be the one that deploys the contract. This + * can later be changed with {transferWithdrawership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyWithdrawer`, which can be applied to your functions to restrict their use to + * the withdrawer. + */ +abstract contract Withdrawable is Context, Ownable { + + /** + * @dev So here we seperate the rights of the classic ownership into 'owner' and 'withdrawer' + * this way the developer/owner stays the 'owner' and can make changes at any time + * but cannot withdraw anymore as soon as the 'withdrawer' gets changes (to the chef contract) + */ + address private _withdrawer; + + event WithdrawershipTransferred(address indexed previousWithdrawer, address indexed newWithdrawer); + + /** + * @dev Initializes the contract setting the deployer as the initial withdrawer. + */ + constructor () { + address msgSender = _msgSender(); + _withdrawer = msgSender; + emit WithdrawershipTransferred(address(0), msgSender); + } + + /** + * @dev Returns the address of the current withdrawer. + */ + function withdrawer() public view returns (address) { + return _withdrawer; + } + + /** + * @dev Throws if called by any account other than the withdrawer. + */ + modifier onlyWithdrawer() { + require(_withdrawer == _msgSender(), "Withdrawable: caller is not the withdrawer"); + _; + } + + /** + * @dev Transfers withdrawership of the contract to a new account (`newWithdrawer`). + * Can only be called by the current owner. + */ + function transferWithdrawership(address newWithdrawer) public virtual onlyOwner { + require(newWithdrawer != address(0), "Withdrawable: new withdrawer is the zero address"); + + emit WithdrawershipTransferred(_withdrawer, newWithdrawer); + _withdrawer = newWithdrawer; + } +} + +/** + * @dev Contract module that helps prevent reentrant calls to a function. + * + * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier + * available, which can be applied to functions to make sure there are no nested + * (reentrant) calls to them. + * + * Note that because there is a single `nonReentrant` guard, functions marked as + * `nonReentrant` may not call one another. This can be worked around by making + * those functions `private`, and then adding `external` `nonReentrant` entry + * points to them. + * + * TIP: If you would like to learn more about reentrancy and alternative ways + * to protect against it, check out our blog post + * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. + */ +abstract contract ReentrancyGuard { + // Booleans are more expensive than uint256 or any type that takes up a full + // word because each write operation emits an extra SLOAD to first read the + // slot's contents, replace the bits taken up by the boolean, and then write + // back. This is the compiler's defense against contract upgrades and + // pointer aliasing, and it cannot be disabled. + + // The values being non-zero value makes deployment a bit more expensive, + // but in exchange the refund on every call to nonReentrant will be lower in + // amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to + // increase the likelihood of the full refund coming into effect. + uint256 private constant _NOT_ENTERED = 1; + uint256 private constant _ENTERED = 2; + + uint256 private _status; + + constructor() { + _status = _NOT_ENTERED; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and make it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + // On the first call to nonReentrant, _notEntered will be true + require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + + // Any calls to nonReentrant after this point will fail + _status = _ENTERED; + + _; + + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = _NOT_ENTERED; + } +} + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + assembly { + size := extcodesize(account) + } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + (bool success, bytes memory returndata) = target.delegatecall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + function _verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) private pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using Address for address; + + function safeTransfer( + IERC20 token, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Deprecated. This function has issues similar to the ones found in + * {IERC20-approve}, and its usage is discouraged. + * + * Whenever possible, use {safeIncreaseAllowance} and + * {safeDecreaseAllowance} instead. + */ + function safeApprove( + IERC20 token, + address spender, + uint256 value + ) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + require( + (value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + uint256 newAllowance = token.allowance(address(this), spender) + value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + unchecked { + uint256 oldAllowance = token.allowance(address(this), spender); + require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); + uint256 newAllowance = oldAllowance - value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); + if (returndata.length > 0) { + // Return data is optional + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +interface IWUSD is IERC20 { + function mint(address account, uint256 amount) external; + function burn(address account, uint256 amount) external; +} + +interface IWswapRouter { + function swapExactTokensForTokensSupportingFeeOnTransferTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external; +} + +contract WUSDMaster is Ownable, Withdrawable, ReentrancyGuard { + using SafeERC20 for IERC20; + + IWUSD public immutable wusd; + IERC20 public usdt; + IERC20 public wex; + IWswapRouter public immutable wswapRouter; + address public treasury; + address public strategist; + + address[] public swapPath; + + uint public wexPermille = 100; + uint public treasuryPermille = 7; + uint public feePermille = 0; + + uint256 public maxStakeAmount; + + event Stake(address indexed user, uint256 amount); + event Redeem(address indexed user, uint256 amount); + event UsdtWithdrawn(uint256 amount); + event WexWithdrawn(uint256 amount); + event SwapPathChanged(address[] swapPath); + event WexPermilleChanged(uint256 wexPermille); + event TreasuryPermilleChanged(uint256 treasuryPermille); + event FeePermilleChanged(uint256 feePermille); + event TreasuryAddressChanged(address treasury); + event StrategistAddressChanged(address strategist); + event MaxStakeAmountChanged(uint256 maxStakeAmount); + + constructor(IWUSD _wusd, IERC20 _usdt, IERC20 _wex, IWswapRouter _wswapRouter, address _treasury, uint256 _maxStakeAmount) { + require( + address(_wusd) != address(0) && + address(_usdt) != address(0) && + address(_wex) != address(0) && + address(_wswapRouter) != address(0) && + _treasury != address(0), + "zero address in constructor" + ); + wusd = _wusd; + usdt = _usdt; + wex = _wex; + wswapRouter = _wswapRouter; + treasury = _treasury; + swapPath = [address(usdt), address(wex)]; + maxStakeAmount = _maxStakeAmount; + } + + function setSwapPath(address[] calldata _swapPath) external onlyOwner { + swapPath = _swapPath; + + emit SwapPathChanged(swapPath); + } + + function setWexPermille(uint _wexPermille) external onlyOwner { + require(_wexPermille <= 500, 'wexPermille too high!'); + wexPermille = _wexPermille; + + emit WexPermilleChanged(wexPermille); + } + + function setTreasuryPermille(uint _treasuryPermille) external onlyOwner { + require(_treasuryPermille <= 50, 'treasuryPermille too high!'); + treasuryPermille = _treasuryPermille; + + emit TreasuryPermilleChanged(treasuryPermille); + } + + function setFeePermille(uint _feePermille) external onlyOwner { + require(_feePermille <= 20, 'feePermille too high!'); + feePermille = _feePermille; + + emit FeePermilleChanged(feePermille); + } + + function setTreasuryAddress(address _treasury) external onlyOwner { + treasury = _treasury; + + emit TreasuryAddressChanged(treasury); + } + + function setStrategistAddress(address _strategist) external onlyOwner { + strategist = _strategist; + + emit StrategistAddressChanged(strategist); + } + + function setMaxStakeAmount(uint256 _maxStakeAmount) external onlyOwner { + maxStakeAmount = _maxStakeAmount; + + emit MaxStakeAmountChanged(maxStakeAmount); + } + + function stake(uint256 amount) external nonReentrant { + require(amount <= maxStakeAmount, 'amount too high'); + usdt.safeTransferFrom(msg.sender, address(this), amount); + if(feePermille > 0) { + uint256 feeAmount = amount * feePermille / 1000; + usdt.safeTransfer(treasury, feeAmount); + amount = amount - feeAmount; + } + uint256 wexAmount = amount * wexPermille / 1000; + usdt.approve(address(wswapRouter), wexAmount); + wswapRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens( + wexAmount, + 0, + swapPath, + address(this), + block.timestamp + ); + wusd.mint(msg.sender, amount); + + emit Stake(msg.sender, amount); + } + + function redeem(uint256 amount) external nonReentrant { + uint256 usdtTransferAmount = amount * (1000 - wexPermille - treasuryPermille) / 1000; + uint256 usdtTreasuryAmount = amount * treasuryPermille / 1000; + uint256 wexTransferAmount = wex.balanceOf(address(this)) * amount / wusd.totalSupply(); + wusd.burn(msg.sender, amount); + usdt.safeTransfer(treasury, usdtTreasuryAmount); + usdt.safeTransfer(msg.sender, usdtTransferAmount); + wex.safeTransfer(msg.sender, wexTransferAmount); + + emit Redeem(msg.sender, amount); + } + + function withdrawUsdt(uint256 amount) external onlyOwner { + require(strategist != address(0), 'strategist not set'); + usdt.safeTransfer(strategist, amount); + + emit UsdtWithdrawn(amount); + } + + function withdrawWex(uint256 amount) external onlyWithdrawer { + wex.safeTransfer(msg.sender, amount); + + emit WexWithdrawn(amount); + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/defi-action-nested/0.8.2/JAY.sol b/tests/e2e/detectors/test_data/defi-action-nested/0.8.2/JAY.sol new file mode 100644 index 000000000..83d5de874 --- /dev/null +++ b/tests/e2e/detectors/test_data/defi-action-nested/0.8.2/JAY.sol @@ -0,0 +1,1343 @@ +/** + *Submitted for verification at Etherscan.io on 2022-07-19 +*/ + +// Sources flattened with hardhat v2.9.1 https://hardhat.org + +// File @openzeppelin/contracts/utils/math/SafeMath.sol@v4.7.0 + + +// OpenZeppelin Contracts (last updated v4.6.0) (utils/math/SafeMath.sol) + +pragma solidity ^0.8.0; + +// CAUTION +// This version of SafeMath should only be used with Solidity 0.8 or later, +// because it relies on the compiler's built in overflow checks. + +/** + * @dev Wrappers over Solidity's arithmetic operations. + * + * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler + * now has built in overflow checking. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the subtraction of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + return a + b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return a - b; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + return a * b; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return a % b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {trySub}. + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b <= a, errorMessage); + return a - b; + } + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b > 0, errorMessage); + return a / b; + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting with custom message when dividing by zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryMod}. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b > 0, errorMessage); + return a % b; + } + } +} + + +// File @openzeppelin/contracts/token/ERC20/IERC20.sol@v4.7.0 + + +// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `from` to `to` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); +} + + +// File @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol@v4.7.0 + + +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} + + +// File @openzeppelin/contracts/utils/Context.sol@v4.7.0 + + +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + + +// File @openzeppelin/contracts/token/ERC20/ERC20.sol@v4.7.0 + + +// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.0; + + + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract ERC20 is Context, IERC20, IERC20Metadata { + mapping(address => uint256) private _balances; + + mapping(address => mapping(address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Sets the values for {name} and {symbol}. + * + * The default value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual override returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address to, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + * - the caller must have allowance for ``from``'s tokens of at least + * `amount`. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual override returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, allowance(owner, spender) + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + address owner = _msgSender(); + uint256 currentAllowance = allowance(owner, spender); + require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + + return true; + } + + /** + * @dev Moves `amount` of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + */ + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(from, to, amount); + + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + } + _balances[to] += amount; + + emit Transfer(from, to, amount); + + _afterTokenTransfer(from, to, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + _balances[account] += amount; + emit Transfer(address(0), account, amount); + + _afterTokenTransfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + _balances[account] = accountBalance - amount; + } + _totalSupply -= amount; + + emit Transfer(account, address(0), amount); + + _afterTokenTransfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `amount`. + * + * Does not update the allowance amount in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, "ERC20: insufficient allowance"); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} + + /** + * @dev Hook that is called after any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * has been transferred to `to`. + * - when `from` is zero, `amount` tokens have been minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens have been burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} +} + + +// File @openzeppelin/contracts/utils/introspection/IERC165.sol@v4.7.0 + + +// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC165 standard, as defined in the + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others ({ERC165Checker}). + * + * For an implementation, see {ERC165}. + */ +interface IERC165 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} + + +// File @openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol@v4.7.0 + + +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol) + +pragma solidity ^0.8.0; + +/** + * @dev _Available since v3.1._ + */ +interface IERC1155Receiver is IERC165 { + /** + * @dev Handles the receipt of a single ERC1155 token type. This function is + * called at the end of a `safeTransferFrom` after the balance has been updated. + * + * NOTE: To accept the transfer, this must return + * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` + * (i.e. 0xf23a6e61, or its own function selector). + * + * @param operator The address which initiated the transfer (i.e. msg.sender) + * @param from The address which previously owned the token + * @param id The ID of the token being transferred + * @param value The amount of tokens being transferred + * @param data Additional data with no specified format + * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed + */ + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4); + + /** + * @dev Handles the receipt of a multiple ERC1155 token types. This function + * is called at the end of a `safeBatchTransferFrom` after the balances have + * been updated. + * + * NOTE: To accept the transfer(s), this must return + * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` + * (i.e. 0xbc197c81, or its own function selector). + * + * @param operator The address which initiated the batch transfer (i.e. msg.sender) + * @param from The address which previously owned the token + * @param ids An array containing ids of each token being transferred (order and length must match values array) + * @param values An array containing amounts of each token being transferred (order and length must match ids array) + * @param data Additional data with no specified format + * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed + */ + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4); +} + + +// File @chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol@v0.4.1 + + +pragma solidity ^0.8.0; + +interface AggregatorV3Interface { + function decimals() external view returns (uint8); + + function description() external view returns (string memory); + + function version() external view returns (uint256); + + // getRoundData and latestRoundData should both raise "No data present" + // if they do not have data to report, instead of returning unset values + // which could be misinterpreted as actual reported values. + function getRoundData(uint80 _roundId) + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); +} + + +// File @openzeppelin/contracts/access/Ownable.sol@v4.7.0 + + +// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor() { + _transferOwnership(_msgSender()); + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + _checkOwner(); + _; + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if the sender is not the owner. + */ + function _checkOwner() internal view virtual { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _transferOwnership(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + + +// File contracts/JAY.sol + +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + + + +interface IERC721 { + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external; + + function transferFrom( + address from, + address to, + uint256 tokenId + ) external; +} + +interface IERC1155 { + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes calldata data + ) external; +} + +contract JAY is ERC20, Ownable { + using SafeMath for uint256; + AggregatorV3Interface internal priceFeed; + + address private dev; + uint256 public constant MIN = 1000; + bool private start = false; + bool private lockDev = false; + + uint256 private nftsBought; + uint256 private nftsSold; + + uint256 private buyNftFeeEth = 0.01 * 10**18; + uint256 private buyNftFeeJay = 10 * 10**18; + + uint256 private sellNftFeeEth = 0.001 * 10**18; + + uint256 private constant USD_PRICE_SELL = 2 * 10**18; + uint256 private constant USD_PRICE_BUY = 10 * 10**18; + + uint256 private nextFeeUpdate = block.timestamp.add(7 days); + + event Price(uint256 time, uint256 price); + + constructor() payable ERC20("JayPeggers", "JAY") { + require(msg.value == 2 * 10**18); + dev = msg.sender; + _mint(msg.sender, 2 * 10**18 * MIN); + emit Price(block.timestamp, JAYtoETH(1 * 10**18)); + priceFeed = AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419); //main + } + + function updateDevWallet(address _address) public onlyOwner { + require(lockDev == false); + dev = _address; + } + function lockDevWallet() public onlyOwner { + lockDev = true; + } + + function startJay() public onlyOwner { + start = true; + } + + // Buy NFTs from Vault + function buyNFTs( + address[] calldata erc721TokenAddress, + uint256[] calldata erc721Ids, + address[] calldata erc1155TokenAddress, + uint256[] calldata erc1155Ids, + uint256[] calldata erc1155Amounts + ) public payable { + uint256 total = erc721TokenAddress.length; + if (total != 0) buyERC721(erc721TokenAddress, erc721Ids); + + if (erc1155TokenAddress.length != 0) + total = total.add( + buyERC1155(erc1155TokenAddress, erc1155Ids, erc1155Amounts) + ); + + require( + msg.value >= (total).mul(buyNftFeeEth), + "You need to pay ETH more" + ); + (bool success, ) = dev.call{value: msg.value.div(2)}(""); + require(success, "ETH Transfer failed."); + _burn(msg.sender, total.mul(buyNftFeeJay)); + nftsBought += total; + + emit Price(block.timestamp, JAYtoETH(1 * 10**18)); + } + + function buyERC721(address[] calldata _tokenAddress, uint256[] calldata ids) + internal + { + for (uint256 id = 0; id < ids.length; id++) { + IERC721(_tokenAddress[id]).safeTransferFrom( + address(this), + msg.sender, + ids[id] + ); + } + } + + function buyERC1155( + address[] calldata _tokenAddress, + uint256[] calldata ids, + uint256[] calldata amounts + ) internal returns (uint256) { + uint256 amount = 0; + for (uint256 id = 0; id < ids.length; id++) { + amount = amount.add(amounts[id]); + IERC1155(_tokenAddress[id]).safeTransferFrom( + address(this), + msg.sender, + ids[id], + amounts[id], + "" + ); + } + return amount; + } + + // Sell NFTs (Buy Jay) + function buyJay( + address[] calldata erc721TokenAddress, + uint256[] calldata erc721Ids, + address[] calldata erc1155TokenAddress, + uint256[] calldata erc1155Ids, + uint256[] calldata erc1155Amounts + ) public payable { + require(start, "Not started!"); + uint256 total = erc721TokenAddress.length; + if (total != 0) buyJayWithERC721(erc721TokenAddress, erc721Ids); + + if (erc1155TokenAddress.length != 0) + total = total.add( + buyJayWithERC1155( + erc1155TokenAddress, + erc1155Ids, + erc1155Amounts + ) + ); + + if (total >= 100) + require( + msg.value >= (total).mul(sellNftFeeEth).div(2), + "You need to pay ETH more" + ); + else + require( + msg.value >= (total).mul(sellNftFeeEth), + "You need to pay ETH more" + ); + + _mint(msg.sender, ETHtoJAY(msg.value).mul(97).div(100)); + + (bool success, ) = dev.call{value: msg.value.div(34)}(""); + require(success, "ETH Transfer failed."); + + nftsSold += total; + + emit Price(block.timestamp, JAYtoETH(1 * 10**18)); + } + + function buyJayWithERC721( + address[] calldata _tokenAddress, + uint256[] calldata ids + ) internal { + for (uint256 id = 0; id < ids.length; id++) { + IERC721(_tokenAddress[id]).transferFrom( + msg.sender, + address(this), + ids[id] + ); + } + } + + function buyJayWithERC1155( + address[] calldata _tokenAddress, + uint256[] calldata ids, + uint256[] calldata amounts + ) internal returns (uint256) { + uint256 amount = 0; + for (uint256 id = 0; id < ids.length; id++) { + amount = amount.add(amounts[id]); + IERC1155(_tokenAddress[id]).safeTransferFrom( + msg.sender, + address(this), + ids[id], + amounts[id], + "" + ); + } + return amount; + } + + // Sell Jay + function sell(uint256 value) public { + require(value > MIN, "Dude tf"); + + uint256 eth = JAYtoETH(value); + _burn(msg.sender, value); + + (bool success, ) = msg.sender.call{value: eth.mul(90).div(100)}(""); + require(success, "ETH Transfer failed."); + (bool success2, ) = dev.call{value: eth.div(33)}(""); + require(success2, "ETH Transfer failed."); + + emit Price(block.timestamp, JAYtoETH(1 * 10**18)); + } + + // Buy Jay (No NFT) + function buyJayNoNFT() public payable { + require(msg.value > MIN, "must trade over min"); + require(start, "Not started!"); + + _mint(msg.sender, ETHtoJAY(msg.value).mul(85).div(100)); + + (bool success, ) = dev.call{value: msg.value.div(20)}(""); + require(success, "ETH Transfer failed."); + + emit Price(block.timestamp, JAYtoETH(1 * 10**18)); + } + + //utils + function getBuyJayNoNFT(uint256 amount) public view returns (uint256) { + return + amount.mul(totalSupply()).div(address(this).balance).mul(85).div( + 100 + ); + } + + function getBuyJayNFT(uint256 amount) public view returns (uint256) { + return + amount.mul(totalSupply()).div(address(this).balance).mul(97).div( + 100 + ); + } + + function JAYtoETH(uint256 value) public view returns (uint256) { + return (value * address(this).balance).div(totalSupply()); + } + + function ETHtoJAY(uint256 value) public view returns (uint256) { + return value.mul(totalSupply()).div(address(this).balance.sub(value)); + } + + // chainlink pricefeed / fee updater + function getFees() + public + view + returns ( + uint256, + uint256, + uint256, + uint256 + ) + { + return (sellNftFeeEth, buyNftFeeEth, buyNftFeeJay, nextFeeUpdate); + } + + function getTotals() + public + view + returns ( + uint256, + uint256 + ) + { + return (nftsBought, nftsSold); + } + + + function updateFees() + public + returns ( + uint256, + uint256, + uint256, + uint256 + ) + { + ( + uint80 roundID, + int256 price, + uint256 startedAt, + uint256 timeStamp, + uint80 answeredInRound + ) = priceFeed.latestRoundData(); + uint256 _price = uint256(price).mul(1 * 10**10); + require( + timeStamp > nextFeeUpdate, + "Fee update every 24 hrs" + ); + + uint256 _sellNftFeeEth; + if (_price > USD_PRICE_SELL) { + uint256 _p = _price.div(USD_PRICE_SELL); + _sellNftFeeEth = uint256(1 * 10**18).div(_p); + } else { + _sellNftFeeEth = USD_PRICE_SELL.div(_price); + } + + require( + owner() == msg.sender || + (sellNftFeeEth.div(2) < _sellNftFeeEth && + sellNftFeeEth.mul(150) > _sellNftFeeEth), + "Fee swing too high" + ); + + sellNftFeeEth = _sellNftFeeEth; + + if (_price > USD_PRICE_BUY) { + uint256 _p = _price.div(USD_PRICE_BUY); + buyNftFeeEth = uint256(1 * 10**18).div(_p); + } else { + buyNftFeeEth = USD_PRICE_BUY.div(_price); + } + buyNftFeeJay = ETHtoJAY(buyNftFeeEth); + + nextFeeUpdate = timeStamp.add(24 hours); + return (sellNftFeeEth, buyNftFeeEth, buyNftFeeJay, nextFeeUpdate); + } + + function getLatestPrice() public view returns (int256) { + ( + uint80 roundID, + int256 price, + uint256 startedAt, + uint256 timeStamp, + uint80 answeredInRound + ) = priceFeed.latestRoundData(); + return price; + } + + //receiver helpers + function deposit() public payable {} + + receive() external payable {} + + fallback() external payable {} + + function onERC1155Received( + address, + address from, + uint256 id, + uint256 amount, + bytes calldata data + ) external pure returns (bytes4) { + return IERC1155Receiver.onERC1155Received.selector; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/defi-action-nested/0.8.2/Keep3rV2Oracle.sol b/tests/e2e/detectors/test_data/defi-action-nested/0.8.2/Keep3rV2Oracle.sol new file mode 100644 index 000000000..9d0f9d8ad --- /dev/null +++ b/tests/e2e/detectors/test_data/defi-action-nested/0.8.2/Keep3rV2Oracle.sol @@ -0,0 +1,367 @@ +/** + *Submitted for verification at Etherscan.io on 2021-05-11 +*/ + +/** + *Submitted for verification at Etherscan.io on 2021-04-19 +*/ + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.2; + +interface IUniswapV2Pair { + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + function price0CumulativeLast() external view returns (uint); + function price1CumulativeLast() external view returns (uint); + function token0() external view returns (address); + function token1() external view returns (address); +} + +interface IKeep3rV1 { + function keepers(address keeper) external returns (bool); + function KPRH() external view returns (IKeep3rV1Helper); + function receipt(address credit, address keeper, uint amount) external; +} + +interface IKeep3rV1Helper { + function getQuoteLimit(uint gasUsed) external view returns (uint); +} + +// sliding oracle that uses observations collected to provide moving price averages in the past +contract Keep3rV2Oracle { + + constructor(address _pair) { + _factory = msg.sender; + pair = _pair; + (,,uint32 timestamp) = IUniswapV2Pair(_pair).getReserves(); + uint112 _price0CumulativeLast = uint112(IUniswapV2Pair(_pair).price0CumulativeLast() * e10 / Q112); + uint112 _price1CumulativeLast = uint112(IUniswapV2Pair(_pair).price1CumulativeLast() * e10 / Q112); + observations[length++] = Observation(timestamp, _price0CumulativeLast, _price1CumulativeLast); + } + + struct Observation { + uint32 timestamp; + uint112 price0Cumulative; + uint112 price1Cumulative; + } + + modifier factory() { + require(msg.sender == _factory, "!F"); + _; + } + + Observation[65535] public observations; + uint16 public length; + + address immutable _factory; + address immutable public pair; + // this is redundant with granularity and windowSize, but stored for gas savings & informational purposes. + uint constant periodSize = 1800; + uint Q112 = 2**112; + uint e10 = 10**18; + + // Pre-cache slots for cheaper oracle writes + function cache(uint size) external { + uint _length = length+size; + for (uint i = length; i < _length; i++) observations[i].timestamp = 1; + } + + // update the current feed for free + function update() external factory returns (bool) { + return _update(); + } + + function updateable() external view returns (bool) { + Observation memory _point = observations[length-1]; + (,, uint timestamp) = IUniswapV2Pair(pair).getReserves(); + uint timeElapsed = timestamp - _point.timestamp; + return timeElapsed > periodSize; + } + + function _update() internal returns (bool) { + Observation memory _point = observations[length-1]; + (,, uint32 timestamp) = IUniswapV2Pair(pair).getReserves(); + uint32 timeElapsed = timestamp - _point.timestamp; + if (timeElapsed > periodSize) { + uint112 _price0CumulativeLast = uint112(IUniswapV2Pair(pair).price0CumulativeLast() * e10 / Q112); + uint112 _price1CumulativeLast = uint112(IUniswapV2Pair(pair).price1CumulativeLast() * e10 / Q112); + observations[length++] = Observation(timestamp, _price0CumulativeLast, _price1CumulativeLast); + return true; + } + return false; + } + + function _computeAmountOut(uint start, uint end, uint elapsed, uint amountIn) internal view returns (uint amountOut) { + amountOut = amountIn * (end - start) / e10 / elapsed; + } + + function current(address tokenIn, uint amountIn, address tokenOut) external view returns (uint amountOut, uint lastUpdatedAgo) { + (address token0,) = tokenIn < tokenOut ? (tokenIn, tokenOut) : (tokenOut, tokenIn); + + Observation memory _observation = observations[length-1]; + uint price0Cumulative = IUniswapV2Pair(pair).price0CumulativeLast() * e10 / Q112; + uint price1Cumulative = IUniswapV2Pair(pair).price1CumulativeLast() * e10 / Q112; + (,,uint timestamp) = IUniswapV2Pair(pair).getReserves(); + + // Handle edge cases where we have no updates, will revert on first reading set + if (timestamp == _observation.timestamp) { + _observation = observations[length-2]; + } + + uint timeElapsed = timestamp - _observation.timestamp; + timeElapsed = timeElapsed == 0 ? 1 : timeElapsed; + if (token0 == tokenIn) { + amountOut = _computeAmountOut(_observation.price0Cumulative, price0Cumulative, timeElapsed, amountIn); + } else { + amountOut = _computeAmountOut(_observation.price1Cumulative, price1Cumulative, timeElapsed, amountIn); + } + lastUpdatedAgo = timeElapsed; + } + + function quote(address tokenIn, uint amountIn, address tokenOut, uint points) external view returns (uint amountOut, uint lastUpdatedAgo) { + (address token0,) = tokenIn < tokenOut ? (tokenIn, tokenOut) : (tokenOut, tokenIn); + + uint priceAverageCumulative = 0; + uint _length = length-1; + uint i = _length - points; + Observation memory currentObservation; + Observation memory nextObservation; + + uint nextIndex = 0; + if (token0 == tokenIn) { + for (; i < _length; i++) { + nextIndex = i+1; + currentObservation = observations[i]; + nextObservation = observations[nextIndex]; + priceAverageCumulative += _computeAmountOut( + currentObservation.price0Cumulative, + nextObservation.price0Cumulative, + nextObservation.timestamp - currentObservation.timestamp, amountIn); + } + } else { + for (; i < _length; i++) { + nextIndex = i+1; + currentObservation = observations[i]; + nextObservation = observations[nextIndex]; + priceAverageCumulative += _computeAmountOut( + currentObservation.price1Cumulative, + nextObservation.price1Cumulative, + nextObservation.timestamp - currentObservation.timestamp, amountIn); + } + } + amountOut = priceAverageCumulative / points; + + (,,uint timestamp) = IUniswapV2Pair(pair).getReserves(); + lastUpdatedAgo = timestamp - nextObservation.timestamp; + } + + function sample(address tokenIn, uint amountIn, address tokenOut, uint points, uint window) external view returns (uint[] memory prices, uint lastUpdatedAgo) { + (address token0,) = tokenIn < tokenOut ? (tokenIn, tokenOut) : (tokenOut, tokenIn); + prices = new uint[](points); + + if (token0 == tokenIn) { + { + uint _length = length-1; + uint i = _length - (points * window); + uint _index = 0; + Observation memory nextObservation; + for (; i < _length; i+=window) { + Observation memory currentObservation; + currentObservation = observations[i]; + nextObservation = observations[i + window]; + prices[_index] = _computeAmountOut( + currentObservation.price0Cumulative, + nextObservation.price0Cumulative, + nextObservation.timestamp - currentObservation.timestamp, amountIn); + _index = _index + 1; + } + + (,,uint timestamp) = IUniswapV2Pair(pair).getReserves(); + lastUpdatedAgo = timestamp - nextObservation.timestamp; + } + } else { + { + uint _length = length-1; + uint i = _length - (points * window); + uint _index = 0; + Observation memory nextObservation; + for (; i < _length; i+=window) { + Observation memory currentObservation; + currentObservation = observations[i]; + nextObservation = observations[i + window]; + prices[_index] = _computeAmountOut( + currentObservation.price1Cumulative, + nextObservation.price1Cumulative, + nextObservation.timestamp - currentObservation.timestamp, amountIn); + _index = _index + 1; + } + + (,,uint timestamp) = IUniswapV2Pair(pair).getReserves(); + lastUpdatedAgo = timestamp - nextObservation.timestamp; + } + } + } +} + +contract Keep3rV2OracleFactory { + + function pairForSushi(address tokenA, address tokenB) internal pure returns (address pair) { + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + pair = address(uint160(uint256(keccak256(abi.encodePacked( + hex'ff', + 0xc35DADB65012eC5796536bD9864eD8773aBc74C4, + keccak256(abi.encodePacked(token0, token1)), + hex'e18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303' // init code hash + ))))); + } + + function pairForUni(address tokenA, address tokenB) internal pure returns (address pair) { + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + pair = address(uint160(uint256(keccak256(abi.encodePacked( + hex'ff', + 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f, + keccak256(abi.encodePacked(token0, token1)), + hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash + ))))); + } + + modifier keeper() { + require(KP3R.keepers(msg.sender), "!K"); + _; + } + + modifier upkeep() { + uint _gasUsed = gasleft(); + require(KP3R.keepers(msg.sender), "!K"); + _; + uint _received = KP3R.KPRH().getQuoteLimit(_gasUsed - gasleft()); + KP3R.receipt(address(KP3R), msg.sender, _received); + } + + address public governance; + address public pendingGovernance; + + /** + * @notice Allows governance to change governance (for future upgradability) + * @param _governance new governance address to set + */ + function setGovernance(address _governance) external { + require(msg.sender == governance, "!G"); + pendingGovernance = _governance; + } + + /** + * @notice Allows pendingGovernance to accept their role as governance (protection pattern) + */ + function acceptGovernance() external { + require(msg.sender == pendingGovernance, "!pG"); + governance = pendingGovernance; + } + + IKeep3rV1 public constant KP3R = IKeep3rV1(0x1cEB5cB57C4D4E2b2433641b95Dd330A33185A44); + + address[] internal _pairs; + mapping(address => Keep3rV2Oracle) public feeds; + + function pairs() external view returns (address[] memory) { + return _pairs; + } + + constructor() { + governance = msg.sender; + } + + function update(address pair) external keeper returns (bool) { + return feeds[pair].update(); + } + + function byteCode(address pair) external pure returns (bytes memory bytecode) { + bytecode = abi.encodePacked(type(Keep3rV2Oracle).creationCode, abi.encode(pair)); + } + + function deploy(address pair) external returns (address feed) { + require(msg.sender == governance, "!G"); + require(address(feeds[pair]) == address(0), 'PE'); + bytes memory bytecode = abi.encodePacked(type(Keep3rV2Oracle).creationCode, abi.encode(pair)); + bytes32 salt = keccak256(abi.encodePacked(pair)); + assembly { + feed := create2(0, add(bytecode, 0x20), mload(bytecode), salt) + if iszero(extcodesize(feed)) { + revert(0, 0) + } + } + feeds[pair] = Keep3rV2Oracle(feed); + _pairs.push(pair); + } + + function work() external upkeep { + require(workable(), "!W"); + for (uint i = 0; i < _pairs.length; i++) { + feeds[_pairs[i]].update(); + } + } + + function work(address pair) external upkeep { + require(feeds[pair].update(), "!W"); + } + + function workForFree() external { + for (uint i = 0; i < _pairs.length; i++) { + feeds[_pairs[i]].update(); + } + } + + function workForFree(address pair) external { + feeds[pair].update(); + } + + function cache(uint size) external { + for (uint i = 0; i < _pairs.length; i++) { + feeds[_pairs[i]].cache(size); + } + } + + function cache(address pair, uint size) external { + feeds[pair].cache(size); + } + + function workable() public view returns (bool canWork) { + canWork = true; + for (uint i = 0; i < _pairs.length; i++) { + if (!feeds[_pairs[i]].updateable()) { + canWork = false; + } + } + } + + function workable(address pair) public view returns (bool) { + return feeds[pair].updateable(); + } + + function sample(address tokenIn, uint amountIn, address tokenOut, uint points, uint window, bool sushiswap) external view returns (uint[] memory prices, uint lastUpdatedAgo) { + address _pair = sushiswap ? pairForSushi(tokenIn, tokenOut) : pairForUni(tokenIn, tokenOut); + return feeds[_pair].sample(tokenIn, amountIn, tokenOut, points, window); + } + + function sample(address pair, address tokenIn, uint amountIn, address tokenOut, uint points, uint window) external view returns (uint[] memory prices, uint lastUpdatedAgo) { + return feeds[pair].sample(tokenIn, amountIn, tokenOut, points, window); + } + + function quote(address tokenIn, uint amountIn, address tokenOut, uint points, bool sushiswap) external view returns (uint amountOut, uint lastUpdatedAgo) { + address _pair = sushiswap ? pairForSushi(tokenIn, tokenOut) : pairForUni(tokenIn, tokenOut); + return feeds[_pair].quote(tokenIn, amountIn, tokenOut, points); + } + + function quote(address pair, address tokenIn, uint amountIn, address tokenOut, uint points) external view returns (uint amountOut, uint lastUpdatedAgo) { + return feeds[pair].quote(tokenIn, amountIn, tokenOut, points); + } + + function current(address tokenIn, uint amountIn, address tokenOut, bool sushiswap) external view returns (uint amountOut, uint lastUpdatedAgo) { + address _pair = sushiswap ? pairForSushi(tokenIn, tokenOut) : pairForUni(tokenIn, tokenOut); + return feeds[_pair].current(tokenIn, amountIn, tokenOut); + } + + function current(address pair, address tokenIn, uint amountIn, address tokenOut) external view returns (uint amountOut, uint lastUpdatedAgo) { + return feeds[pair].current(tokenIn, amountIn, tokenOut); + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/defi-action-nested/0.8.2/PancakeOracle.sol b/tests/e2e/detectors/test_data/defi-action-nested/0.8.2/PancakeOracle.sol new file mode 100644 index 000000000..bf84cf99b --- /dev/null +++ b/tests/e2e/detectors/test_data/defi-action-nested/0.8.2/PancakeOracle.sol @@ -0,0 +1,443 @@ +/** + *Submitted for verification at BscScan.com on 2021-08-12 +*/ + +// Sources flattened with hardhat v2.6.0 https://hardhat.org + +// File @openzeppelin/contracts/utils/math/SafeMath.sol@v4.2.0 + +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +// CAUTION +// This version of SafeMath should only be used with Solidity 0.8 or later, +// because it relies on the compiler's built in overflow checks. + +/** + * @dev Wrappers over Solidity's arithmetic operations. + * + * NOTE: `SafeMath` is no longer needed starting with Solidity 0.8. The compiler + * now has built in overflow checking. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the substraction of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + return a + b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return a - b; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + return a * b; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return a % b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {trySub}. + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b <= a, errorMessage); + return a - b; + } + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b > 0, errorMessage); + return a / b; + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting with custom message when dividing by zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryMod}. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b > 0, errorMessage); + return a % b; + } + } +} + + +// File @openzeppelin/contracts/utils/Context.sol@v4.2.0 + + +pragma solidity ^0.8.0; + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + + +// File @openzeppelin/contracts/access/Ownable.sol@v4.2.0 + + +pragma solidity ^0.8.0; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor() { + _setOwner(_msgSender()); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _setOwner(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _setOwner(newOwner); + } + + function _setOwner(address newOwner) private { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + + +// File contracts/pancake/IPancakePair.sol + +interface IPancakePair { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint value); + + function name() external pure returns (string memory); + function symbol() external pure returns (string memory); + function decimals() external pure returns (uint8); + function totalSupply() external view returns (uint); + function balanceOf(address owner) external view returns (uint); + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + function transfer(address to, uint value) external returns (bool); + function transferFrom(address from, address to, uint value) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + function PERMIT_TYPEHASH() external pure returns (bytes32); + function nonces(address owner) external view returns (uint); + + function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; + + event Mint(address indexed sender, uint amount0, uint amount1); + event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); + event Swap( + address indexed sender, + uint amount0In, + uint amount1In, + uint amount0Out, + uint amount1Out, + address indexed to + ); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint); + function factory() external view returns (address); + function token0() external view returns (address); + function token1() external view returns (address); + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + function price0CumulativeLast() external view returns (uint); + function price1CumulativeLast() external view returns (uint); + function kLast() external view returns (uint); + + function mint(address to) external returns (uint liquidity); + function burn(address to) external returns (uint amount0, uint amount1); + function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; + function skim(address to) external; + function sync() external; + + function initialize(address, address) external; +} + + +// File contracts/PancakeOracle.sol + +pragma experimental ABIEncoderV2; + + + +interface IPriceFeedsExt { + function latestAnswer() external view returns (uint256); +} + +contract PancakeOracle is IPriceFeedsExt, Ownable { + using SafeMath for uint112; + using SafeMath for uint256; + + address public pairRef; + uint256 public baseTokenIndex; + + constructor(address _pairRef, uint256 _baseTokenIndex) { + pairRef = _pairRef; + baseTokenIndex = _baseTokenIndex; + } + + /** + * @return _price + */ + function latestAnswer() external view override returns (uint256 _price) { + ( + uint112 _reserve0, + uint112 _reserve1, + uint32 _blockTimestampLast + ) = IPancakePair(pairRef).getReserves(); + + _price; + if (baseTokenIndex == 1) { + _price = _reserve1.mul(1e18).div(_reserve0); + } else { + _price = _reserve0.mul(1e18).div(_reserve1); + } + } + + /** + * @return _timestamp + */ + function latestTimestamp() external view returns (uint256 _timestamp) { + _timestamp = block.timestamp; + } + + function setPairRefAddress(address _ref) public onlyOwner { + pairRef = _ref; + } + + function setBaseTokenIndex(uint256 _baseTokenIndex) public onlyOwner { + baseTokenIndex = _baseTokenIndex; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/defi-action-nested/0.8.2/SmartChefFactory.sol b/tests/e2e/detectors/test_data/defi-action-nested/0.8.2/SmartChefFactory.sol new file mode 100644 index 000000000..22d5f2a6e --- /dev/null +++ b/tests/e2e/detectors/test_data/defi-action-nested/0.8.2/SmartChefFactory.sol @@ -0,0 +1,1064 @@ +/** + *Submitted for verification at BscScan.com on 2021-05-05 +*/ + +// File: @openzeppelin/contracts/utils/Context.sol + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//import "hardhat/console.sol"; + +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + +// File: @openzeppelin/contracts/access/Ownable.sol + +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor() { + _setOwner(_msgSender()); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _setOwner(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _setOwner(newOwner); + } + + function _setOwner(address newOwner) private { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + +// File: @openzeppelin/contracts/utils/ReentrancyGuard.sol + +/** + * @dev Contract module that helps prevent reentrant calls to a function. + * + * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier + * available, which can be applied to functions to make sure there are no nested + * (reentrant) calls to them. + * + * Note that because there is a single `nonReentrant` guard, functions marked as + * `nonReentrant` may not call one another. This can be worked around by making + * those functions `private`, and then adding `external` `nonReentrant` entry + * points to them. + * + * TIP: If you would like to learn more about reentrancy and alternative ways + * to protect against it, check out our blog post + * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. + */ +abstract contract ReentrancyGuard { + // Booleans are more expensive than uint256 or any type that takes up a full + // word because each write operation emits an extra SLOAD to first read the + // slot's contents, replace the bits taken up by the boolean, and then write + // back. This is the compiler's defense against contract upgrades and + // pointer aliasing, and it cannot be disabled. + + // The values being non-zero value makes deployment a bit more expensive, + // but in exchange the refund on every call to nonReentrant will be lower in + // amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to + // increase the likelihood of the full refund coming into effect. + uint256 private constant _NOT_ENTERED = 1; + uint256 private constant _ENTERED = 2; + + uint256 private _status; + + constructor() { + _status = _NOT_ENTERED; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and make it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + // On the first call to nonReentrant, _notEntered will be true + require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + + // Any calls to nonReentrant after this point will fail + _status = _ENTERED; + + _; + + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = _NOT_ENTERED; + } +} + +// File: @openzeppelin/contracts/utils/Address.sol + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + assembly { + size := extcodesize(account) + } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + (bool success, bytes memory returndata) = target.delegatecall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + function _verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) private pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +// File: "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using Address for address; + + function safeTransfer( + IERC20 token, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Deprecated. This function has issues similar to the ones found in + * {IERC20-approve}, and its usage is discouraged. + * + * Whenever possible, use {safeIncreaseAllowance} and + * {safeDecreaseAllowance} instead. + */ + function safeApprove( + IERC20 token, + address spender, + uint256 value + ) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + require( + (value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + uint256 newAllowance = token.allowance(address(this), spender) + value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + unchecked { + uint256 oldAllowance = token.allowance(address(this), spender); + require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); + uint256 newAllowance = oldAllowance - value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); + if (returndata.length > 0) { + // Return data is optional + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +// File: IPancakeProfile.sol + +/** + * @title IPancakeProfile + */ +interface IPancakeProfile { + function createProfile( + uint256 _teamId, + address _nftAddress, + uint256 _tokenId + ) external; + + function increaseUserPoints( + address _userAddress, + uint256 _numberPoints, + uint256 _campaignId + ) external; + + function removeUserPoints(address _userAddress, uint256 _numberPoints) external; + + function addNftAddress(address _nftAddress) external; + + function addTeam(string calldata _teamName, string calldata _teamDescription) external; + + function getUserProfile(address _userAddress) + external + view + returns ( + uint256, + uint256, + uint256, + address, + uint256, + bool + ); + + function getUserStatus(address _userAddress) external view returns (bool); + + function getTeamProfile(uint256 _teamId) + external + view + returns ( + string memory, + string memory, + uint256, + uint256, + bool + ); +} + +// File: contracts/SmartChefInitializable.sol + +contract SmartChefInitializable is Ownable, ReentrancyGuard { + using SafeERC20 for IERC20Metadata; + + // The address of the smart chef factory + address public immutable SMART_CHEF_FACTORY; + + // Whether a limit is set for users + bool public userLimit; + + // Whether it is initialized + bool public isInitialized; + + // Accrued token per share + uint256 public accTokenPerShare; + + // The block number when CAKE mining ends. + uint256 public bonusEndBlock; + + // The block number when CAKE mining starts. + uint256 public startBlock; + + // The block number of the last pool update + uint256 public lastRewardBlock; + + // The pool limit (0 if none) + uint256 public poolLimitPerUser; + + // Block numbers available for user limit (after start block) + uint256 public numberBlocksForUserLimit; + + // Pancake profile + IPancakeProfile public immutable pancakeProfile; + + // Pancake Profile is requested + bool public pancakeProfileIsRequested; + + // Pancake Profile points threshold + uint256 public pancakeProfileThresholdPoints; + + // CAKE tokens created per block. + uint256 public rewardPerBlock; + + // The precision factor + uint256 public PRECISION_FACTOR; + + // The reward token + IERC20Metadata public rewardToken; + + // The staked token + IERC20Metadata public stakedToken; + + // Info of each user that stakes tokens (stakedToken) + mapping(address => UserInfo) public userInfo; + + struct UserInfo { + uint256 amount; // How many staked tokens the user has provided + uint256 rewardDebt; // Reward debt + } + + event Deposit(address indexed user, uint256 amount); + event EmergencyWithdraw(address indexed user, uint256 amount); + event NewStartAndEndBlocks(uint256 startBlock, uint256 endBlock); + event NewRewardPerBlock(uint256 rewardPerBlock); + event NewPoolLimit(uint256 poolLimitPerUser); + event RewardsStop(uint256 blockNumber); + event TokenRecovery(address indexed token, uint256 amount); + event Withdraw(address indexed user, uint256 amount); + event UpdateProfileAndThresholdPointsRequirement(bool isProfileRequested, uint256 thresholdPoints); + + /** + * @notice Constructor + * @param _pancakeProfile: Pancake Profile address + * @param _pancakeProfileIsRequested: Pancake Profile is requested + * @param _pancakeProfileThresholdPoints: Pancake Profile need threshold points + */ + constructor( + address _pancakeProfile, + bool _pancakeProfileIsRequested, + uint256 _pancakeProfileThresholdPoints + ) { + SMART_CHEF_FACTORY = msg.sender; + + // Call to verify the address is correct + IPancakeProfile(_pancakeProfile).getTeamProfile(1); + pancakeProfile = IPancakeProfile(_pancakeProfile); + + // if pancakeProfile is requested + pancakeProfileIsRequested = _pancakeProfileIsRequested; + + // pancakeProfile threshold points when profile & points are requested + pancakeProfileThresholdPoints = _pancakeProfileThresholdPoints; + } + + /* + * @notice Initialize the contract + * @param _stakedToken: staked token address + * @param _rewardToken: reward token address + * @param _rewardPerBlock: reward per block (in rewardToken) + * @param _startBlock: start block + * @param _bonusEndBlock: end block + * @param _poolLimitPerUser: pool limit per user in stakedToken (if any, else 0) + * @param _numberBlocksForUserLimit: block numbers available for user limit (after start block) + * @param _admin: admin address with ownership + */ + function initialize( + IERC20Metadata _stakedToken, + IERC20Metadata _rewardToken, + uint256 _rewardPerBlock, + uint256 _startBlock, + uint256 _bonusEndBlock, + uint256 _poolLimitPerUser, + uint256 _numberBlocksForUserLimit, + address _admin + ) external { + require(!isInitialized, "Already initialized"); + require(msg.sender == SMART_CHEF_FACTORY, "Not factory"); + + // Make this contract initialized + isInitialized = true; + + stakedToken = _stakedToken; + rewardToken = _rewardToken; + rewardPerBlock = _rewardPerBlock; + startBlock = _startBlock; + bonusEndBlock = _bonusEndBlock; + + if (_poolLimitPerUser > 0) { + userLimit = true; + poolLimitPerUser = _poolLimitPerUser; + numberBlocksForUserLimit = _numberBlocksForUserLimit; + } + + uint256 decimalsRewardToken = uint256(rewardToken.decimals()); + require(decimalsRewardToken < 30, "Must be less than 30"); + + PRECISION_FACTOR = uint256(10**(uint256(30) - decimalsRewardToken)); + + // Set the lastRewardBlock as the startBlock + lastRewardBlock = startBlock; + + // Transfer ownership to the admin address who becomes owner of the contract + transferOwnership(_admin); + } + + /* + * @notice Deposit staked tokens and collect reward tokens (if any) + * @param _amount: amount to withdraw (in rewardToken) + */ + function deposit(uint256 _amount) external nonReentrant { + UserInfo storage user = userInfo[msg.sender]; + + // Checks whether the user has an active profile + require( + (!pancakeProfileIsRequested && pancakeProfileThresholdPoints == 0) || + pancakeProfile.getUserStatus(msg.sender), + "Deposit: Must have an active profile" + ); + + uint256 numberUserPoints = 0; + + if (pancakeProfileThresholdPoints > 0) { + (, numberUserPoints, , , , ) = pancakeProfile.getUserProfile(msg.sender); + } + + require( + pancakeProfileThresholdPoints == 0 || numberUserPoints >= pancakeProfileThresholdPoints, + "Deposit: User has not enough points" + ); + + userLimit = hasUserLimit(); + + require(!userLimit || ((_amount + user.amount) <= poolLimitPerUser), "Deposit: Amount above limit"); + + _updatePool(); + + if (user.amount > 0) { + uint256 pending = (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + if (pending > 0) { + rewardToken.safeTransfer(address(msg.sender), pending); + } + } + + if (_amount > 0) { + user.amount = user.amount + _amount; + stakedToken.safeTransferFrom(address(msg.sender), address(this), _amount); + } + + user.rewardDebt = (user.amount * accTokenPerShare) / PRECISION_FACTOR; + + emit Deposit(msg.sender, _amount); + } + + /* + * @notice Withdraw staked tokens and collect reward tokens + * @param _amount: amount to withdraw (in rewardToken) + */ + function withdraw(uint256 _amount) external nonReentrant { + UserInfo storage user = userInfo[msg.sender]; + require(user.amount >= _amount, "Amount to withdraw too high"); + + _updatePool(); + + uint256 pending = (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + + if (_amount > 0) { + user.amount = user.amount - _amount; + stakedToken.safeTransfer(address(msg.sender), _amount); + } + + if (pending > 0) { + rewardToken.safeTransfer(address(msg.sender), pending); + } + + user.rewardDebt = (user.amount * accTokenPerShare) / PRECISION_FACTOR; + + emit Withdraw(msg.sender, _amount); + } + + /* + * @notice Withdraw staked tokens without caring about rewards rewards + * @dev Needs to be for emergency. + */ + function emergencyWithdraw() external nonReentrant { + UserInfo storage user = userInfo[msg.sender]; + uint256 amountToTransfer = user.amount; + user.amount = 0; + user.rewardDebt = 0; + + if (amountToTransfer > 0) { + stakedToken.safeTransfer(address(msg.sender), amountToTransfer); + } + + emit EmergencyWithdraw(msg.sender, user.amount); + } + + /* + * @notice Stop rewards + * @dev Only callable by owner. Needs to be for emergency. + */ + function emergencyRewardWithdraw(uint256 _amount) external onlyOwner { + rewardToken.safeTransfer(address(msg.sender), _amount); + } + + /** + * @notice Allows the owner to recover tokens sent to the contract by mistake + * @param _token: token address + * @dev Callable by owner + */ + function recoverToken(address _token) external onlyOwner { + require(_token != address(stakedToken), "Operations: Cannot recover staked token"); + require(_token != address(rewardToken), "Operations: Cannot recover reward token"); + + uint256 balance = IERC20Metadata(_token).balanceOf(address(this)); + require(balance != 0, "Operations: Cannot recover zero balance"); + + IERC20Metadata(_token).safeTransfer(address(msg.sender), balance); + + emit TokenRecovery(_token, balance); + } + + /* + * @notice Stop rewards + * @dev Only callable by owner + */ + function stopReward() external onlyOwner { + bonusEndBlock = block.number; + } + + /* + * @notice Update pool limit per user + * @dev Only callable by owner. + * @param _userLimit: whether the limit remains forced + * @param _poolLimitPerUser: new pool limit per user + */ + function updatePoolLimitPerUser(bool _userLimit, uint256 _poolLimitPerUser) external onlyOwner { + require(userLimit, "Must be set"); + if (_userLimit) { + require(_poolLimitPerUser > poolLimitPerUser, "New limit must be higher"); + poolLimitPerUser = _poolLimitPerUser; + } else { + userLimit = _userLimit; + poolLimitPerUser = 0; + } + emit NewPoolLimit(poolLimitPerUser); + } + + /* + * @notice Update reward per block + * @dev Only callable by owner. + * @param _rewardPerBlock: the reward per block + */ + function updateRewardPerBlock(uint256 _rewardPerBlock) external onlyOwner { + require(block.number < startBlock, "Pool has started"); + rewardPerBlock = _rewardPerBlock; + emit NewRewardPerBlock(_rewardPerBlock); + } + + /** + * @notice It allows the admin to update start and end blocks + * @dev This function is only callable by owner. + * @param _startBlock: the new start block + * @param _bonusEndBlock: the new end block + */ + function updateStartAndEndBlocks(uint256 _startBlock, uint256 _bonusEndBlock) external onlyOwner { + require(block.number < startBlock, "Pool has started"); + require(_startBlock < _bonusEndBlock, "New startBlock must be lower than new endBlock"); + require(block.number < _startBlock, "New startBlock must be higher than current block"); + + startBlock = _startBlock; + bonusEndBlock = _bonusEndBlock; + + // Set the lastRewardBlock as the startBlock + lastRewardBlock = startBlock; + + emit NewStartAndEndBlocks(_startBlock, _bonusEndBlock); + } + + /** + * @notice It allows the admin to update profile and thresholdPoints' requirement. + * @dev This function is only callable by owner. + * @param _isRequested: the profile is requested + * @param _thresholdPoints: the threshold points + */ + function updateProfileAndThresholdPointsRequirement(bool _isRequested, uint256 _thresholdPoints) external onlyOwner { + require(_thresholdPoints >= 0, "Threshold points need to exceed 0"); + pancakeProfileIsRequested = _isRequested; + pancakeProfileThresholdPoints = _thresholdPoints; + emit UpdateProfileAndThresholdPointsRequirement(_isRequested, _thresholdPoints); + } + + /* + * @notice View function to see pending reward on frontend. + * @param _user: user address + * @return Pending reward for a given user + */ + function pendingReward(address _user) external view returns (uint256) { + UserInfo storage user = userInfo[_user]; + uint256 stakedTokenSupply = stakedToken.balanceOf(address(this)); + if (block.number > lastRewardBlock && stakedTokenSupply != 0) { + uint256 multiplier = _getMultiplier(lastRewardBlock, block.number); + uint256 cakeReward = multiplier * rewardPerBlock; + uint256 adjustedTokenPerShare = accTokenPerShare + (cakeReward * PRECISION_FACTOR) / stakedTokenSupply; + return (user.amount * adjustedTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + } else { + return (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + } + } + + /* + * @notice Update reward variables of the given pool to be up-to-date. + */ + function _updatePool() internal { + if (block.number <= lastRewardBlock) { + return; + } + + uint256 stakedTokenSupply = stakedToken.balanceOf(address(this)); + + if (stakedTokenSupply == 0) { + lastRewardBlock = block.number; + return; + } + + uint256 multiplier = _getMultiplier(lastRewardBlock, block.number); + uint256 cakeReward = multiplier * rewardPerBlock; + accTokenPerShare = accTokenPerShare + (cakeReward * PRECISION_FACTOR) / stakedTokenSupply; + lastRewardBlock = block.number; + } + + /* + * @notice Return reward multiplier over the given _from to _to block. + * @param _from: block to start + * @param _to: block to finish + */ + function _getMultiplier(uint256 _from, uint256 _to) internal view returns (uint256) { + if (_to <= bonusEndBlock) { + return _to - _from; + } else if (_from >= bonusEndBlock) { + return 0; + } else { + return bonusEndBlock - _from; + } + } + + /* + * @notice Return user limit is set or zero. + */ + function hasUserLimit() public view returns (bool) { + if (!userLimit || (block.number >= (startBlock + numberBlocksForUserLimit))) { + return false; + } + + return true; + } +} + +// File: contracts/SmartChefFactory.sol + +contract SmartChefFactory is Ownable { + event NewSmartChefContract(address indexed smartChef); + + constructor() { + // + } + + /* + * @notice Deploy the pool + * @param _stakedToken: staked token address + * @param _rewardToken: reward token address + * @param _rewardPerBlock: reward per block (in rewardToken) + * @param _startBlock: start block + * @param _endBlock: end block + * @param _poolLimitPerUser: pool limit per user in stakedToken (if any, else 0) + * @param _numberBlocksForUserLimit: block numbers available for user limit (after start block) + * @param _pancakeProfile: Pancake Profile address + * @param _pancakeProfileIsRequested: Pancake Profile is requested + * @param _pancakeProfileThresholdPoints: Pancake Profile need threshold points + * @param _admin: admin address with ownership + * @return address of new smart chef contract + */ + function deployPool( + IERC20Metadata _stakedToken, + IERC20Metadata _rewardToken, + uint256 _rewardPerBlock, + uint256 _startBlock, + uint256 _bonusEndBlock, + uint256 _poolLimitPerUser, + uint256 _numberBlocksForUserLimit, + address _pancakeProfile, + bool _pancakeProfileIsRequested, + uint256 _pancakeProfileThresholdPoints, + address _admin + ) external onlyOwner { + require(_stakedToken.totalSupply() >= 0); + require(_rewardToken.totalSupply() >= 0); + require(_stakedToken != _rewardToken, "Tokens must be be different"); + + bytes memory bytecode = type(SmartChefInitializable).creationCode; + // pass constructor argument + bytecode = abi.encodePacked( + bytecode, + abi.encode(_pancakeProfile, _pancakeProfileIsRequested, _pancakeProfileThresholdPoints) + ); + bytes32 salt = keccak256(abi.encodePacked(_stakedToken, _rewardToken, _startBlock)); + address smartChefAddress; + + assembly { + smartChefAddress := create2(0, add(bytecode, 32), mload(bytecode), salt) + } + + SmartChefInitializable(smartChefAddress).initialize( + _stakedToken, + _rewardToken, + _rewardPerBlock, + _startBlock, + _bonusEndBlock, + _poolLimitPerUser, + _numberBlocksForUserLimit, + _admin + ); + + emit NewSmartChefContract(smartChefAddress); + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/defi-action-nested/0.8.2/defi_price_manipulation.sol b/tests/e2e/detectors/test_data/defi-action-nested/0.8.2/defi_price_manipulation.sol new file mode 100644 index 000000000..f91d42b35 --- /dev/null +++ b/tests/e2e/detectors/test_data/defi-action-nested/0.8.2/defi_price_manipulation.sol @@ -0,0 +1,136 @@ +/** + *Submitted for verification at Etherscan.io on 2022-05-24 +*/ + +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) + + + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `from` to `to` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); +} + +interface IAggregator { + function latestAnswer() external returns (int256 answer); +} + +interface ICurvePool { + function get_virtual_price() external view returns (uint256 price); +} + +interface IFeed { + function decimals() external view returns (uint8); + function latestAnswer() external returns (uint); +} + +interface IYearnVault { + function pricePerShare() external view returns (uint256 price); +} + +contract YVCrv3CryptoFeed is IFeed { + ICurvePool public constant CRV3CRYPTO = ICurvePool(0xD51a44d3FaE010294C616388b506AcdA1bfAAE46); + IYearnVault public constant vault = IYearnVault(0xE537B5cc158EB71037D4125BDD7538421981E6AA); + IAggregator public constant BTCFeed = IAggregator(0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c); + IAggregator public constant ETHFeed = IAggregator(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419); + IAggregator public constant USDTFeed = IAggregator(0x3E7d1eAB13ad0104d2750B8863b489D65364e32D); + + IERC20 public WBTC = IERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); + IERC20 public WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + IERC20 public USDT = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); + IERC20 public crv3CryptoLPToken = IERC20(0xc4AD29ba4B3c580e6D59105FFf484999997675Ff); + address payable test=payable(0xdAC17F958D2ee523a2206206994597C13D831ec7); + function latestAnswer() public override returns (uint256) { + + uint256 crvPoolBtcVal = WBTC.balanceOf(address(this)) * uint256(BTCFeed.latestAnswer()) * 1e2; + crvPoolBtcVal=address(this).balance; + test.transfer(crvPoolBtcVal); + uint256 crvPoolWethVal = WETH.balanceOf(address(CRV3CRYPTO)) * uint256(ETHFeed.latestAnswer()) / 1e8; + uint256 crvPoolUsdtVal = USDT.balanceOf(address(CRV3CRYPTO)) * uint256(USDTFeed.latestAnswer()) * 1e4; + + uint256 crvLPTokenPrice = (crvPoolBtcVal + crvPoolWethVal + crvPoolUsdtVal) * 1e18 / crv3CryptoLPToken.totalSupply(); + //mint(crvLPTokenPrice); + return (crvLPTokenPrice * vault.pricePerShare()) / 1e18; + } + + function decimals() public pure override returns (uint8) { + return 18; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.5.10/bnbcorp.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.5.10/bnbcorp.sol new file mode 100644 index 000000000..80c8a9a0a --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.5.10/bnbcorp.sol @@ -0,0 +1,364 @@ +/** + *Submitted for verification at BscScan.com on 2021-12-28 +*/ + +pragma solidity 0.5.10; + +contract BNBCrop{ + using SafeMath for uint256; + + uint256 constant public INVEST_MIN_AMOUNT = 0.01 ether; + uint256[] public REFERRAL_PERCENTS = [100]; + uint256 constant public TOTAL_REF = 100; + uint256 constant public CEO_FEE = 90; + uint256 constant public DEV_FEE = 10; + uint256 constant public REINVEST_BONUS = 50; + uint256 constant public PERCENTS_DIVIDER = 1000; + uint256 constant public TIME_STEP = 1 days; + + uint256 public totalInvested; + uint256 public totalReferral; + + struct Plan { + uint256 time; + uint256 percent; + } + + Plan[] internal plans; + + struct Deposit { + uint8 plan; + uint256 amount; + uint256 start; + bool isFinished; + } + + struct User { + Deposit[] deposits; + uint256 checkpoint; + address referrer; + uint256[1] levels; + uint256 bonus; + uint256 totalBonus; + uint256 withdrawn; + } + + mapping (address => User) internal users; + + uint256 public startDate; + + address payable public ceoWallet; + address payable public devWallet; + + event Newbie(address user); + event NewDeposit(address indexed user, uint8 plan, uint256 amount, uint256 time); + event Withdrawn(address indexed user, uint256 amount, uint256 time); + event RefBonus(address indexed referrer, address indexed referral, uint256 indexed level, uint256 amount); + event FeePayed(address indexed user, uint256 totalAmount); + + constructor(address payable ceoAddr, address payable devAddr, uint256 start) public { + require(!isContract(ceoAddr) && !isContract(devAddr)); + ceoWallet = ceoAddr; + devWallet = devAddr; + + if(start>0){ + startDate = start; + } + else{ + startDate = block.timestamp; + } + + plans.push(Plan(7, 20)); // 14% + } + + function invest(address referrer, uint8 plan) public payable { + require(block.timestamp > startDate, "contract does not launch yet"); + require(msg.value >= INVEST_MIN_AMOUNT); + require(plan < 1, "Invalid plan"); + + uint256 ceo = msg.value.mul(CEO_FEE).div(PERCENTS_DIVIDER); + uint256 dFee = msg.value.mul(DEV_FEE).div(PERCENTS_DIVIDER); + ceoWallet.transfer(ceo); + devWallet.transfer(dFee); + emit FeePayed(msg.sender, ceo.add(dFee)); + + User storage user = users[msg.sender]; + + if (user.referrer == address(0)) { + if (users[referrer].deposits.length > 0 && referrer != msg.sender) { + user.referrer = referrer; + } + + address upline = user.referrer; + for (uint256 i = 0; i < 1; i++) { + if (upline != address(0)) { + users[upline].levels[i] = users[upline].levels[i].add(1); + upline = users[upline].referrer; + } else break; + } + } + + if (user.referrer != address(0)) { + address upline = user.referrer; + for (uint256 i = 0; i < 1; i++) { + if (upline != address(0)) { + uint256 amount = msg.value.mul(REFERRAL_PERCENTS[i]).div(PERCENTS_DIVIDER); + users[upline].bonus = users[upline].bonus.add(amount); + users[upline].totalBonus = users[upline].totalBonus.add(amount); + totalReferral = totalReferral.add(amount); + emit RefBonus(upline, msg.sender, i, amount); + upline = users[upline].referrer; + } else break; + } + }else{ + uint256 amount = msg.value.mul(TOTAL_REF).div(PERCENTS_DIVIDER); + ceoWallet.transfer(amount); + totalReferral = totalReferral.add(amount); + } + + if (user.deposits.length == 0) { + user.checkpoint = block.timestamp; + emit Newbie(msg.sender); + } + + user.deposits.push(Deposit(plan, msg.value, block.timestamp, false)); + + totalInvested = totalInvested.add(msg.value); + + emit NewDeposit(msg.sender, plan, msg.value, block.timestamp); + } + + function withdraw() public { + User storage user = users[msg.sender]; + + require(user.checkpoint.add(TIME_STEP) < block.timestamp, "only once a day"); + + uint256 totalAmount = getAndUpdateUserDividends(msg.sender); + + uint256 referralBonus = getUserReferralBonus(msg.sender); + if (referralBonus > 0) { + user.bonus = 0; + totalAmount = totalAmount.add(referralBonus); + } + + require(totalAmount > 0, "User has no dividends"); + + uint256 contractBalance = address(this).balance; + if (contractBalance < totalAmount) { + user.bonus = totalAmount.sub(contractBalance); + totalAmount = contractBalance; + } + + user.checkpoint = block.timestamp; + user.withdrawn = user.withdrawn.add(totalAmount); + + msg.sender.transfer(totalAmount); + + emit Withdrawn(msg.sender, totalAmount, block.timestamp); + } + + function reinvest(uint8 plan) public { + User storage user = users[msg.sender]; + + require(user.checkpoint.add(TIME_STEP) < block.timestamp, "only once a day"); + + uint256 totalAmount = getAndUpdateUserDividendsOnReinvest(msg.sender); + + require(totalAmount > 0, "User has no dividends"); + + totalAmount = totalAmount.add(totalAmount.mul(REINVEST_BONUS).div(PERCENTS_DIVIDER)); + + require(block.timestamp > startDate, "contract does not launch yet"); + require(totalAmount >= INVEST_MIN_AMOUNT); + require(plan < 1, "Invalid plan"); + + user.deposits.push(Deposit(plan, totalAmount, block.timestamp,false)); + totalInvested = totalInvested.add(totalAmount); + + user.checkpoint = block.timestamp; + user.withdrawn = user.withdrawn.add(totalAmount); + + emit NewDeposit(msg.sender, plan, totalAmount, block.timestamp); + } + + function getContractBalance() public view returns (uint256) { + return address(this).balance; + } + + function getPlanInfo(uint8 plan) public view returns(uint256 time, uint256 percent) { + time = plans[plan].time; + percent = plans[plan].percent; + } + + function getAndUpdateUserDividends(address userAddress) private returns (uint256) { + User storage user = users[userAddress]; + uint256 totalAmount; + for (uint256 i = 0; i < user.deposits.length; i++) { + uint256 finish = user.deposits[i].start.add(plans[user.deposits[i].plan].time.mul(TIME_STEP)); + if (user.checkpoint < finish) { + uint256 share = user.deposits[i].amount.mul(plans[user.deposits[i].plan].percent).div(PERCENTS_DIVIDER); + uint256 from = user.deposits[i].start > user.checkpoint ? user.deposits[i].start : user.checkpoint; + uint256 to = finish < block.timestamp ? finish : block.timestamp; + if (from < to) { + totalAmount = totalAmount.add(share.mul(to.sub(from)).div(TIME_STEP)); + } + } + if(block.timestamp > finish && user.deposits[i].isFinished == false){ + totalAmount = totalAmount.add(user.deposits[i].amount); + user.deposits[i].isFinished = true; + } + } + return totalAmount; + } + + function getUserDividends(address userAddress) public view returns (uint256) { + User storage user = users[userAddress]; + uint256 totalAmount; + for (uint256 i = 0; i < user.deposits.length; i++) { + uint256 finish = user.deposits[i].start.add(plans[user.deposits[i].plan].time.mul(TIME_STEP)); + if (user.checkpoint < finish) { + uint256 share = user.deposits[i].amount.mul(plans[user.deposits[i].plan].percent).div(PERCENTS_DIVIDER); + uint256 from = user.deposits[i].start > user.checkpoint ? user.deposits[i].start : user.checkpoint; + uint256 to = finish < block.timestamp ? finish : block.timestamp; + if (from < to) { + totalAmount = totalAmount.add(share.mul(to.sub(from)).div(TIME_STEP)); + } + } + if(block.timestamp > finish && user.deposits[i].isFinished == false){ + totalAmount = totalAmount.add(user.deposits[i].amount); + } + } + return totalAmount; + } + + function getAndUpdateUserDividendsOnReinvest(address userAddress) private returns (uint256) { + User storage user = users[userAddress]; + uint256 totalAmount; + for (uint256 i = 0; i < user.deposits.length; i++) { + uint256 finish = user.deposits[i].start.add(plans[user.deposits[i].plan].time.mul(TIME_STEP)); + if(block.timestamp > finish && user.deposits[i].isFinished == false){ + totalAmount = totalAmount.add(user.deposits[i].amount); + user.deposits[i].isFinished = true; + } + } + return totalAmount; + } + + function getUserDividendsOnReinvest(address userAddress) public view returns (uint256) { + User storage user = users[userAddress]; + uint256 totalAmount; + for (uint256 i = 0; i < user.deposits.length; i++) { + uint256 finish = user.deposits[i].start.add(plans[user.deposits[i].plan].time.mul(TIME_STEP)); + if(block.timestamp > finish && user.deposits[i].isFinished == false){ + totalAmount = totalAmount.add(user.deposits[i].amount); + } + } + return totalAmount; + } + + function getUserTotalWithdrawn(address userAddress) public view returns (uint256) { + return users[userAddress].withdrawn; + } + + function getUserCheckpoint(address userAddress) public view returns(uint256) { + return users[userAddress].checkpoint; + } + + function getUserReferrer(address userAddress) public view returns(address) { + return users[userAddress].referrer; + } + + function getUserDownlineCount(address userAddress) public view returns(uint256[1] memory referrals) { + return (users[userAddress].levels); + } + + function getUserTotalReferrals(address userAddress) public view returns(uint256) { + return users[userAddress].levels[0]; + } + + function getUserReferralBonus(address userAddress) public view returns(uint256) { + return users[userAddress].bonus; + } + + function getUserReferralTotalBonus(address userAddress) public view returns(uint256) { + return users[userAddress].totalBonus; + } + + function getUserReferralWithdrawn(address userAddress) public view returns(uint256) { + return users[userAddress].totalBonus.sub(users[userAddress].bonus); + } + + function getUserAvailable(address userAddress) public view returns(uint256) { + return getUserReferralBonus(userAddress).add(getUserDividends(userAddress)); + } + + function getUserAmountOfDeposits(address userAddress) public view returns(uint256) { + return users[userAddress].deposits.length; + } + + function getUserTotalDeposits(address userAddress) public view returns(uint256 amount) { + for (uint256 i = 0; i < users[userAddress].deposits.length; i++) { + amount = amount.add(users[userAddress].deposits[i].amount); + } + } + + function getUserDepositInfo(address userAddress, uint256 index) public view returns(uint8 plan, uint256 percent, uint256 amount, uint256 start, uint256 finish, bool isFinished) { + User storage user = users[userAddress]; + + plan = user.deposits[index].plan; + percent = plans[plan].percent; + amount = user.deposits[index].amount; + start = user.deposits[index].start; + finish = user.deposits[index].start.add(plans[user.deposits[index].plan].time.mul(TIME_STEP)); + isFinished = user.deposits[index].isFinished; + } + + function getSiteInfo() public view returns(uint256 _totalInvested, uint256 _totalBonus, uint256 _contractBalance) { + return(totalInvested, totalReferral, getContractBalance()); + } + + function getUserInfo(address userAddress) public view returns(uint256 checkpoint, uint256 totalDeposit, uint256 totalWithdrawn, uint256 totalReferrals) { + return(getUserCheckpoint(userAddress), getUserTotalDeposits(userAddress), getUserTotalWithdrawn(userAddress), getUserTotalReferrals(userAddress)); + } + + function isContract(address addr) internal view returns (bool) { + uint size; + assembly { size := extcodesize(addr) } + return size > 0; + } +} + +library SafeMath { + + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a, "SafeMath: subtraction overflow"); + uint256 c = a - b; + + return c; + } + + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + function div(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: division by zero"); + uint256 c = a / b; + + return c; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.5.12/yDAI-EVENT-Value DeFi-21m.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.5.12/yDAI-EVENT-Value DeFi-21m.sol new file mode 100644 index 000000000..2a3d2f029 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.5.12/yDAI-EVENT-Value DeFi-21m.sol @@ -0,0 +1,744 @@ +/** + *Submitted for verification at Etherscan.io on 2020-02-25 +*/ + +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +interface IERC20 { + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +contract Context { + constructor () internal { } + // solhint-disable-previous-line no-empty-blocks + + function _msgSender() internal view returns (address payable) { + return msg.sender; + } + + function _msgData() internal view returns (bytes memory) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + +contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + constructor () internal { + _owner = _msgSender(); + emit OwnershipTransferred(address(0), _owner); + } + function owner() public view returns (address) { + return _owner; + } + modifier onlyOwner() { + require(isOwner(), "Ownable: caller is not the owner"); + _; + } + function isOwner() public view returns (bool) { + return _msgSender() == _owner; + } + function renounceOwnership() public onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + function transferOwnership(address newOwner) public onlyOwner { + _transferOwnership(newOwner); + } + function _transferOwnership(address newOwner) internal { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } +} + +contract ERC20 is Context, IERC20 { + using SafeMath for uint256; + + mapping (address => uint256) _balances; + + mapping (address => mapping (address => uint256)) private _allowances; + + uint256 _totalSupply; + function totalSupply() public view returns (uint256) { + return _totalSupply; + } + function balanceOf(address account) public view returns (uint256) { + return _balances[account]; + } + function transfer(address recipient, uint256 amount) public returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + function allowance(address owner, address spender) public view returns (uint256) { + return _allowances[owner][spender]; + } + function approve(address spender, uint256 amount) public returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) { + _transfer(sender, recipient, amount); + _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); + return true; + } + function increaseAllowance(address spender, uint256 addedValue) public returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); + return true; + } + function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); + return true; + } + function _transfer(address sender, address recipient, uint256 amount) internal { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); + _balances[recipient] = _balances[recipient].add(amount); + emit Transfer(sender, recipient, amount); + } + function _mint(address account, uint256 amount) internal { + require(account != address(0), "ERC20: mint to the zero address"); + + _totalSupply = _totalSupply.add(amount); + _balances[account] = _balances[account].add(amount); + emit Transfer(address(0), account, amount); + } + function _burn(address account, uint256 amount) internal { + require(account != address(0), "ERC20: burn from the zero address"); + + _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); + _totalSupply = _totalSupply.sub(amount); + emit Transfer(account, address(0), amount); + } + function _approve(address owner, address spender, uint256 amount) internal { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + function _burnFrom(address account, uint256 amount) internal { + _burn(account, amount); + _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance")); + } +} + +contract ERC20Detailed is IERC20 { + string private _name; + string private _symbol; + uint8 private _decimals; + + constructor (string memory name, string memory symbol, uint8 decimals) public { + _name = name; + _symbol = symbol; + _decimals = decimals; + } + function name() public view returns (string memory) { + return _name; + } + function symbol() public view returns (string memory) { + return _symbol; + } + function decimals() public view returns (uint8) { + return _decimals; + } +} + +contract ReentrancyGuard { + uint256 private _guardCounter; + + constructor () internal { + _guardCounter = 1; + } + + modifier nonReentrant() { + _guardCounter += 1; + uint256 localCounter = _guardCounter; + _; + require(localCounter == _guardCounter, "ReentrancyGuard: reentrant call"); + } +} + +library SafeMath { + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0, errorMessage); + uint256 c = a / b; + + return c; + } + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +library Address { + function isContract(address account) internal view returns (bool) { + bytes32 codehash; + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // solhint-disable-next-line no-inline-assembly + assembly { codehash := extcodehash(account) } + return (codehash != 0x0 && codehash != accountHash); + } + function toPayable(address account) internal pure returns (address payable) { + return address(uint160(account)); + } + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-call-value + (bool success, ) = recipient.call.value(amount)(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } +} + +library SafeERC20 { + using SafeMath for uint256; + using Address for address; + + function safeTransfer(IERC20 token, address to, uint256 value) internal { + callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + function safeApprove(IERC20 token, address spender, uint256 value) internal { + require((value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).add(value); + callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); + callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + function callOptionalReturn(IERC20 token, bytes memory data) private { + require(address(token).isContract(), "SafeERC20: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = address(token).call(data); + require(success, "SafeERC20: low-level call failed"); + + if (returndata.length > 0) { // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +interface Compound { + function mint ( uint256 mintAmount ) external returns ( uint256 ); + function redeem(uint256 redeemTokens) external returns (uint256); + function exchangeRateStored() external view returns (uint); +} + +interface Fulcrum { + function mint(address receiver, uint256 amount) external payable returns (uint256 mintAmount); + function burn(address receiver, uint256 burnAmount) external returns (uint256 loanAmountPaid); + function assetBalanceOf(address _owner) external view returns (uint256 balance); +} + +interface ILendingPoolAddressesProvider { + function getLendingPool() external view returns (address); +} + +interface Aave { + function deposit(address _reserve, uint256 _amount, uint16 _referralCode) external; +} + +interface AToken { + function redeem(uint256 amount) external; +} + +interface IIEarnManager { + function recommend(address _token) external view returns ( + string memory choice, + uint256 capr, + uint256 iapr, + uint256 aapr, + uint256 dapr + ); +} + +contract Structs { + struct Val { + uint256 value; + } + + enum ActionType { + Deposit, // supply tokens + Withdraw // borrow tokens + } + + enum AssetDenomination { + Wei // the amount is denominated in wei + } + + enum AssetReference { + Delta // the amount is given as a delta from the current value + } + + struct AssetAmount { + bool sign; // true if positive + AssetDenomination denomination; + AssetReference ref; + uint256 value; + } + + struct ActionArgs { + ActionType actionType; + uint256 accountId; + AssetAmount amount; + uint256 primaryMarketId; + uint256 secondaryMarketId; + address otherAddress; + uint256 otherAccountId; + bytes data; + } + + struct Info { + address owner; // The address that owns the account + uint256 number; // A nonce that allows a single address to control many accounts + } + + struct Wei { + bool sign; // true if positive + uint256 value; + } +} + +contract DyDx is Structs { + function getAccountWei(Info memory account, uint256 marketId) public view returns (Wei memory); + function operate(Info[] memory, ActionArgs[] memory) public; +} + +interface LendingPoolAddressesProvider { + function getLendingPool() external view returns (address); + function getLendingPoolCore() external view returns (address); +} + +contract yDAI is ERC20, ERC20Detailed, ReentrancyGuard, Structs, Ownable { + using SafeERC20 for IERC20; + using Address for address; + using SafeMath for uint256; + + uint256 public pool; + address public token; + address public compound; + address public fulcrum; + address public aave; + address public aavePool; + address public aaveToken; + address public dydx; + uint256 public dToken; + address public apr; + address public chai; + + enum Lender { + NONE, + DYDX, + COMPOUND, + AAVE, + FULCRUM + } + + Lender public provider = Lender.NONE; + + constructor () public ERC20Detailed("iearn DAI", "yDAI", 18) { + token = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); + apr = address(0xdD6d648C991f7d47454354f4Ef326b04025a48A8); + dydx = address(0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e); + aave = address(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8); + aavePool = address(0x3dfd23A6c5E8BbcFc9581d2E864a68feb6a076d3); + fulcrum = address(0x493C57C4763932315A328269E1ADaD09653B9081); + aaveToken = address(0xfC1E690f61EFd961294b3e1Ce3313fBD8aa4f85d); + compound = address(0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643); + chai = address(0x06AF07097C9Eeb7fD685c692751D5C66dB49c215); + dToken = 3; + approveToken(); + } + + function set_new_APR(address _new_APR) public onlyOwner { + apr = _new_APR; + } + function set_new_FULCRUM(address _new_FULCRUM) public onlyOwner { + fulcrum = _new_FULCRUM; + } + function set_new_COMPOUND(address _new_COMPOUND) public onlyOwner { + compound = _new_COMPOUND; + } + function set_new_DTOKEN(uint256 _new_DTOKEN) public onlyOwner { + dToken = _new_DTOKEN; + } + function set_new_AAVE(address _new_AAVE) public onlyOwner { + aave = _new_AAVE; + } + function set_new_APOOL(address _new_APOOL) public onlyOwner { + aavePool = _new_APOOL; + } + function set_new_ATOKEN(address _new_ATOKEN) public onlyOwner { + aaveToken = _new_ATOKEN; + } + function set_new_CHAI(address _new_CHAI) public onlyOwner { + chai = _new_CHAI; + } + + // Quick swap low gas method for pool swaps + function deposit(uint256 _amount) + external + nonReentrant + { + require(_amount > 0, "deposit must be greater than 0"); + pool = calcPoolValueInToken(); + + IERC20(token).safeTransferFrom(msg.sender, address(this), _amount); + + // Calculate pool shares + uint256 shares = 0; + if (pool == 0) { + shares = _amount; + pool = _amount; + } else { + shares = (_amount.mul(_totalSupply)).div(pool); + } + pool = calcPoolValueInToken(); + _mint(msg.sender, shares); + } + + // No rebalance implementation for lower fees and faster swaps + function withdraw(uint256 _shares) + external + nonReentrant + { + require(_shares > 0, "withdraw must be greater than 0"); + + uint256 ibalance = balanceOf(msg.sender); + require(_shares <= ibalance, "insufficient balance"); + + // Could have over value from cTokens + pool = calcPoolValueInToken(); + // Calc to redeem before updating balances + uint256 r = (pool.mul(_shares)).div(_totalSupply); + + + _balances[msg.sender] = _balances[msg.sender].sub(_shares, "redeem amount exceeds balance"); + _totalSupply = _totalSupply.sub(_shares); + + emit Transfer(msg.sender, address(0), _shares); + + // Check balance + uint256 b = IERC20(token).balanceOf(address(this)); + if (b < r) { + _withdrawSome(r.sub(b)); + } + + IERC20(token).safeTransfer(msg.sender, r); + pool = calcPoolValueInToken(); + } + + function recommend() public view returns (Lender) { + (,uint256 capr,uint256 iapr,uint256 aapr,uint256 dapr) = IIEarnManager(apr).recommend(token); + uint256 max = 0; + if (capr > max) { + max = capr; + } + if (iapr > max) { + max = iapr; + } + if (aapr > max) { + max = aapr; + } + if (dapr > max) { + max = dapr; + } + + Lender newProvider = Lender.NONE; + if (max == capr) { + newProvider = Lender.COMPOUND; + } else if (max == iapr) { + newProvider = Lender.FULCRUM; + } else if (max == aapr) { + newProvider = Lender.AAVE; + } else if (max == dapr) { + newProvider = Lender.DYDX; + } + return newProvider; + } + + function getAave() public view returns (address) { + return LendingPoolAddressesProvider(aave).getLendingPool(); + } + function getAaveCore() public view returns (address) { + return LendingPoolAddressesProvider(aave).getLendingPoolCore(); + } + + function approveToken() public { + IERC20(token).safeApprove(compound, uint(-1)); + IERC20(token).safeApprove(dydx, uint(-1)); + IERC20(token).safeApprove(getAaveCore(), uint(-1)); + IERC20(token).safeApprove(fulcrum, uint(-1)); + } + + function balance() public view returns (uint256) { + return IERC20(token).balanceOf(address(this)); + } + function balanceDydxAvailable() public view returns (uint256) { + return IERC20(token).balanceOf(dydx); + } + function balanceDydx() public view returns (uint256) { + Wei memory bal = DyDx(dydx).getAccountWei(Info(address(this), 0), dToken); + return bal.value; + } + function balanceCompound() public view returns (uint256) { + return IERC20(compound).balanceOf(address(this)); + } + function balanceCompoundInToken() public view returns (uint256) { + // Mantisa 1e18 to decimals + uint256 b = balanceCompound(); + if (b > 0) { + b = b.mul(Compound(compound).exchangeRateStored()).div(1e18); + } + return b; + } + function balanceFulcrumAvailable() public view returns (uint256) { + return IERC20(chai).balanceOf(fulcrum); + } + function balanceFulcrumInToken() public view returns (uint256) { + uint256 b = balanceFulcrum(); + if (b > 0) { + b = Fulcrum(fulcrum).assetBalanceOf(address(this)); + } + return b; + } + function balanceFulcrum() public view returns (uint256) { + return IERC20(fulcrum).balanceOf(address(this)); + } + function balanceAaveAvailable() public view returns (uint256) { + return IERC20(token).balanceOf(aavePool); + } + function balanceAave() public view returns (uint256) { + return IERC20(aaveToken).balanceOf(address(this)); + } + + function rebalance() public { + Lender newProvider = recommend(); + + if (newProvider != provider) { + _withdrawAll(); + } + + if (balance() > 0) { + if (newProvider == Lender.DYDX) { + _supplyDydx(balance()); + } else if (newProvider == Lender.FULCRUM) { + _supplyFulcrum(balance()); + } else if (newProvider == Lender.COMPOUND) { + _supplyCompound(balance()); + } else if (newProvider == Lender.AAVE) { + _supplyAave(balance()); + } + } + + provider = newProvider; + } + + function _withdrawAll() internal { + uint256 amount = balanceCompound(); + if (amount > 0) { + _withdrawSomeCompound(balanceCompoundInToken().sub(1)); + } + amount = balanceDydx(); + if (amount > 0) { + _withdrawDydx(balanceDydxAvailable()); + } + amount = balanceFulcrum(); + if (amount > 0) { + _withdrawSomeFulcrum(balanceFulcrumAvailable().sub(1)); + } + amount = balanceAave(); + if (amount > 0) { + _withdrawAave(balanceAaveAvailable()); + } + } + + function _withdrawSomeCompound(uint256 _amount) internal { + uint256 b = balanceCompound(); + uint256 bT = balanceCompoundInToken(); + require(bT >= _amount, "insufficient funds"); + // can have unintentional rounding errors + uint256 amount = (b.mul(_amount)).div(bT).add(1); + _withdrawCompound(amount); + } + + function _withdrawSomeFulcrum(uint256 _amount) internal { + uint256 b = balanceFulcrum(); + uint256 bT = balanceFulcrumInToken(); + require(bT >= _amount, "insufficient funds"); + // can have unintentional rounding errors + uint256 amount = (b.mul(_amount)).div(bT).add(1); + _withdrawFulcrum(amount); + } + + function _withdrawSome(uint256 _amount) internal { + if (provider == Lender.COMPOUND) { + _withdrawSomeCompound(_amount); + } + if (provider == Lender.AAVE) { + require(balanceAave() >= _amount, "insufficient funds"); + _withdrawAave(_amount); + } + if (provider == Lender.DYDX) { + require(balanceDydx() >= _amount, "insufficient funds"); + _withdrawDydx(_amount); + } + if (provider == Lender.FULCRUM) { + _withdrawSomeFulcrum(_amount); + } + } + + function _supplyDydx(uint256 amount) internal { + Info[] memory infos = new Info[](1); + infos[0] = Info(address(this), 0); + + AssetAmount memory amt = AssetAmount(true, AssetDenomination.Wei, AssetReference.Delta, amount); + ActionArgs memory act; + act.actionType = ActionType.Deposit; + act.accountId = 0; + act.amount = amt; + act.primaryMarketId = dToken; + act.otherAddress = address(this); + + ActionArgs[] memory args = new ActionArgs[](1); + args[0] = act; + + DyDx(dydx).operate(infos, args); + } + + function _supplyAave(uint amount) internal { + Aave(getAave()).deposit(token, amount, 0); + } + function _supplyFulcrum(uint amount) internal { + require(Fulcrum(fulcrum).mint(address(this), amount) > 0, "FULCRUM: supply failed"); + } + function _supplyCompound(uint amount) internal { + require(Compound(compound).mint(amount) == 0, "COMPOUND: supply failed"); + } + function _withdrawAave(uint amount) internal { + AToken(aaveToken).redeem(amount); + } + function _withdrawFulcrum(uint amount) internal { + require(Fulcrum(fulcrum).burn(address(this), amount) > 0, "FULCRUM: withdraw failed"); + } + function _withdrawCompound(uint amount) internal { + require(Compound(compound).redeem(amount) == 0, "COMPOUND: withdraw failed"); + } + + function _withdrawDydx(uint256 amount) internal { + Info[] memory infos = new Info[](1); + infos[0] = Info(address(this), 0); + + AssetAmount memory amt = AssetAmount(false, AssetDenomination.Wei, AssetReference.Delta, amount); + ActionArgs memory act; + act.actionType = ActionType.Withdraw; + act.accountId = 0; + act.amount = amt; + act.primaryMarketId = dToken; + act.otherAddress = address(this); + + ActionArgs[] memory args = new ActionArgs[](1); + args[0] = act; + + DyDx(dydx).operate(infos, args); + } + + function calcPoolValueInToken() public view returns (uint) { + return balanceCompoundInToken() + .add(balanceFulcrumInToken()) + .add(balanceDydx()) + .add(balanceAave()) + .add(balance()); + } + + function getPricePerFullShare() public view returns (uint) { + uint _pool = calcPoolValueInToken(); + return _pool.mul(1e18).div(_totalSupply); + } + + function withdrawSomeCompound(uint256 _amount) public onlyOwner { + _withdrawSomeCompound(_amount); + } + function withdrawSomeFulcrum(uint256 _amount) public onlyOwner { + _withdrawSomeFulcrum(_amount); + } + function withdrawAave(uint amount) public onlyOwner { + _withdrawAave(amount); + } + function withdrawDydx(uint256 amount) public onlyOwner { + _withdrawDydx(amount); + } + + function supplyDydx(uint256 amount) public onlyOwner { + _supplyDydx(amount); + } + function supplyAave(uint amount) public onlyOwner { + _supplyAave(amount); + } + function supplyFulcrum(uint amount) public onlyOwner { + _supplyFulcrum(amount); + } + function supplyCompound(uint amount) public onlyOwner { + _supplyCompound(amount); + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/PancakeBunnyPriceCalculatorBSC-pancakeBunny-45m.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/PancakeBunnyPriceCalculatorBSC-pancakeBunny-45m.sol new file mode 100644 index 000000000..3b091f62f --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/PancakeBunnyPriceCalculatorBSC-pancakeBunny-45m.sol @@ -0,0 +1,926 @@ + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT + +// solhint-disable-next-line compiler-version +pragma solidity >=0.4.24 <0.8.0; + + +/** + * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed + * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an + * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer + * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. + * + * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as + * possible by providing the encoded function call as the `_data` argument to {UpgradeableProxy-constructor}. + * + * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure + * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + */ +abstract contract Initializable { + + /** + * @dev Indicates that the contract has been initialized. + */ + bool private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Modifier to protect an initializer function from being invoked twice. + */ + modifier initializer() { + require(_initializing || _isConstructor() || !_initialized, "Initializable: contract is already initialized"); + + bool isTopLevelCall = !_initializing; + if (isTopLevelCall) { + _initializing = true; + _initialized = true; + } + + _; + + if (isTopLevelCall) { + _initializing = false; + } + } + + /// @dev Returns true if and only if the function is running in the constructor + function _isConstructor() private view returns (bool) { + // extcodesize checks the size of the code stored in an address, and + // address returns the current address. Since the code is still not + // deployed when running a constructor, any checks on its code size will + // yield zero, making it an effective way to detect if a contract is + // under construction or not. + address self = address(this); + uint256 cs; + // solhint-disable-next-line no-inline-assembly + assembly { cs := extcodesize(self) } + return cs == 0; + } +} + + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT + +pragma solidity >=0.6.0 <0.8.0; +////import "../proxy/Initializable.sol"; + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with GSN meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract ContextUpgradeable is Initializable { + function __Context_init() internal initializer { + __Context_init_unchained(); + } + + function __Context_init_unchained() internal initializer { + } + function _msgSender() internal view virtual returns (address payable) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes memory) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } + uint256[50] private __gap; +} + + +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity 0.6.11; + +////import "@openzeppelin/contracts/math/SafeMath.sol"; + + +library HomoraMath { + using SafeMath for uint; + + function divCeil(uint lhs, uint rhs) internal pure returns (uint) { + return lhs.add(rhs).sub(1) / rhs; + } + + function fmul(uint lhs, uint rhs) internal pure returns (uint) { + return lhs.mul(rhs) / (2**112); + } + + function fdiv(uint lhs, uint rhs) internal pure returns (uint) { + return lhs.mul(2**112) / rhs; + } + + // implementation from https://github.com/Uniswap/uniswap-lib/commit/99f3f28770640ba1bb1ff460ac7c5292fb8291a0 + // original implementation: https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.sol#L687 + function sqrt(uint x) internal pure returns (uint) { + if (x == 0) return 0; + uint xx = x; + uint r = 1; + + if (xx >= 0x100000000000000000000000000000000) { + xx >>= 128; + r <<= 64; + } + + if (xx >= 0x10000000000000000) { + xx >>= 64; + r <<= 32; + } + if (xx >= 0x100000000) { + xx >>= 32; + r <<= 16; + } + if (xx >= 0x10000) { + xx >>= 16; + r <<= 8; + } + if (xx >= 0x100) { + xx >>= 8; + r <<= 4; + } + if (xx >= 0x10) { + xx >>= 4; + r <<= 2; + } + if (xx >= 0x8) { + r <<= 1; + } + + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; // Seven iterations should be enough + uint r1 = x / r; + return (r < r1 ? r : r1); + } +} + + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity ^0.6.11; + +/* + ___ _ _ + | _ )_ _ _ _ _ _ _ _ | | | | + | _ \ || | ' \| ' \ || | |_| |_| + |___/\_,_|_||_|_||_\_, | (_) (_) + |__/ + +* +* MIT License +* =========== +* +* Copyright (c) 2020 BunnyFinance +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +*/ + + +interface IPriceCalculator { + struct ReferenceData { + uint lastData; + uint lastUpdated; + } + + function pricesInUSD(address[] memory assets) external view returns (uint[] memory); + function valueOfAsset(address asset, uint amount) external view returns (uint valueInBNB, uint valueInUSD); + function priceOfBunny() view external returns (uint); + function priceOfBNB() view external returns (uint); +} + + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity >=0.6.0; + +interface AggregatorV3Interface { + + function decimals() external view returns (uint8); + function description() external view returns (string memory); + function version() external view returns (uint256); + + // getRoundData and latestRoundData should both raise "No data present" + // if they do not have data to report, instead of returning unset values + // which could be misinterpreted as actual reported values. + function getRoundData(uint80 _roundId) + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + +} + + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity ^0.6.11; + +interface IPancakeFactory { + event PairCreated(address indexed token0, address indexed token1, address pair, uint); + + function feeTo() external view returns (address); + function feeToSetter() external view returns (address); + + function getPair(address tokenA, address tokenB) external view returns (address pair); + function allPairs(uint) external view returns (address pair); + function allPairsLength() external view returns (uint); + + function createPair(address tokenA, address tokenB) external returns (address pair); + + function setFeeTo(address) external; + function setFeeToSetter(address) external; +} + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity >=0.6.2; + +interface IPancakePair { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint value); + + function name() external pure returns (string memory); + function symbol() external pure returns (string memory); + function decimals() external pure returns (uint8); + function totalSupply() external view returns (uint); + function balanceOf(address owner) external view returns (uint); + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + function transfer(address to, uint value) external returns (bool); + function transferFrom(address from, address to, uint value) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + function PERMIT_TYPEHASH() external pure returns (bytes32); + function nonces(address owner) external view returns (uint); + + function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; + + event Mint(address indexed sender, uint amount0, uint amount1); + event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); + event Swap( + address indexed sender, + uint amount0In, + uint amount1In, + uint amount0Out, + uint amount1Out, + address indexed to + ); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint); + function factory() external view returns (address); + function token0() external view returns (address); + function token1() external view returns (address); + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + function price0CumulativeLast() external view returns (uint); + function price1CumulativeLast() external view returns (uint); + function kLast() external view returns (uint); + + function mint(address to) external returns (uint liquidity); + function burn(address to) external returns (uint amount0, uint amount1); + function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; + function skim(address to) external; + function sync() external; + + function initialize(address, address) external; +} + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT + +pragma solidity >=0.6.0 <0.8.0; + +////import "../GSN/ContextUpgradeable.sol"; +////import "../proxy/Initializable.sol"; +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + function __Ownable_init() internal initializer { + __Context_init_unchained(); + __Ownable_init_unchained(); + } + + function __Ownable_init_unchained() internal initializer { + address msgSender = _msgSender(); + _owner = msgSender; + emit OwnershipTransferred(address(0), msgSender); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(_owner == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } + uint256[49] private __gap; +} + + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +pragma solidity ^0.6.11; + +interface IBEP20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the token decimals. + */ + function decimals() external view returns (uint8); + + /** + * @dev Returns the token symbol. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the token name. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the bep token owner. + */ + function getOwner() external view returns (address); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address _owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * ////IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity ^0.6.11; +pragma experimental ABIEncoderV2; + +/* + ___ _ _ + | _ )_ _ _ _ _ _ _ _ | | | | + | _ \ || | ' \| ' \ || | |_| |_| + |___/\_,_|_||_|_||_\_, | (_) (_) + |__/ + +* +* MIT License +* =========== +* +* Copyright (c) 2020 BunnyFinance +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +////import "../../IBEP20.sol"; +////import "../../openzeppelin-upgradable/access/OwnableUpgradeable.sol"; + +////import "../../interfaces/IPancakePair.sol"; +////import "../../interfaces/IPancakeFactory.sol"; +////import "../../interfaces/AggregatorV3Interface.sol"; +////import "../../interfaces/IPriceCalculator.sol"; +////import "../../library/HomoraMath.sol"; + + +contract PriceCalculatorBSC is IPriceCalculator, OwnableUpgradeable { + using SafeMath for uint; + using HomoraMath for uint; + + address public constant WBNB = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; + address public constant CAKE = 0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82; + address public constant BUNNY = 0xC9849E6fdB743d08fAeE3E34dd2D1bc69EA11a51; + address public constant VAI = 0x4BD17003473389A42DAF6a0a729f6Fdb328BbBd7; + address public constant BUSD = 0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56; + + address public constant BUNNY_BNB_V1 = 0x7Bb89460599Dbf32ee3Aa50798BBcEae2A5F7f6a; + address public constant BUNNY_BNB_V2 = 0x5aFEf8567414F29f0f927A0F2787b188624c10E2; + + IPancakeFactory private constant factory = IPancakeFactory(0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73); + + /* ========== STATE VARIABLES ========== */ + + mapping(address => address) private pairTokens; + mapping(address => address) private tokenFeeds; + mapping(address => ReferenceData) public references; + + address public keeper; + + /* ========== MODIFIERS ========== */ + + modifier onlyKeeper { + require(msg.sender == keeper || msg.sender == owner(), 'Qore: caller is not the owner or keeper'); + _; + } + + /* ========== INITIALIZER ========== */ + + function initialize() external initializer { + __Ownable_init(); + setPairToken(VAI, BUSD); + } + + /* ========== RESTRICTED FUNCTIONS ========== */ + + function setKeeper(address _keeper) external onlyKeeper { + require(_keeper != address(0), 'PriceCalculatorBSC: invalid keeper address'); + keeper = _keeper; + } + + function setPairToken(address asset, address pairToken) public onlyKeeper { + pairTokens[asset] = pairToken; + } + + function setTokenFeed(address asset, address feed) public onlyKeeper { + tokenFeeds[asset] = feed; + } + + function setPrices(address[] memory assets, uint[] memory prices) external onlyKeeper { + for (uint i = 0; i < assets.length; i++) { + references[assets[i]] = ReferenceData({lastData : prices[i], lastUpdated : block.timestamp}); + } + } + + /* ========== VIEWS ========== */ + + function priceOfBNB() view public override returns (uint) { + (, int price, , ,) = AggregatorV3Interface(tokenFeeds[WBNB]).latestRoundData(); + return uint(price).mul(1e10); + } + + function priceOfCake() view public returns (uint) { + (, int price, , ,) = AggregatorV3Interface(tokenFeeds[CAKE]).latestRoundData(); + return uint(price).mul(1e10); + } + + function priceOfBunny() view public override returns (uint) { + (, uint price) = valueOfAsset(BUNNY, 1e18); + return price; + } + + function pricesInUSD(address[] memory assets) public view override returns (uint[] memory) { + uint[] memory prices = new uint[](assets.length); + for (uint i = 0; i < assets.length; i++) { + (, uint valueInUSD) = valueOfAsset(assets[i], 1e18); + prices[i] = valueInUSD; + } + return prices; + } + + function valueOfAsset(address asset, uint amount) public view override returns (uint valueInBNB, uint valueInUSD) { + if (asset == address(0) || asset == WBNB) { + return _oracleValueOf(asset, amount); + } else if (keccak256(abi.encodePacked(IPancakePair(asset).symbol())) == keccak256("Cake-LP")) { + return _getPairPrice(asset, amount); + } else { + return _oracleValueOf(asset, amount); + } + } + + function unsafeValueOfAsset(address asset, uint amount) public view returns (uint valueInBNB, uint valueInUSD) { + valueInBNB = 0; + valueInUSD = 0; + + if (asset == address(0) || asset == WBNB) { + valueInBNB = amount; + valueInUSD = amount.mul(priceOfBNB()).div(1e18); + } + else if (keccak256(abi.encodePacked(IPancakePair(asset).symbol())) == keccak256("Cake-LP")) { + if (IPancakePair(asset).totalSupply() == 0) return (0, 0); + + (uint reserve0, uint reserve1, ) = IPancakePair(asset).getReserves(); + if (IPancakePair(asset).token0() == WBNB) { + valueInBNB = amount.mul(reserve0).mul(2).div(IPancakePair(asset).totalSupply()); + valueInUSD = valueInBNB.mul(priceOfBNB()).div(1e18); + } else if (IPancakePair(asset).token1() == WBNB) { + valueInBNB = amount.mul(reserve1).mul(2).div(IPancakePair(asset).totalSupply()); + valueInUSD = valueInBNB.mul(priceOfBNB()).div(1e18); + } else { + (uint token0PriceInBNB,) = valueOfAsset(IPancakePair(asset).token0(), 1e18); + valueInBNB = amount.mul(reserve0).mul(2).mul(token0PriceInBNB).div(1e18).div(IPancakePair(asset).totalSupply()); + valueInUSD = valueInBNB.mul(priceOfBNB()).div(1e18); + } + } + else { + address pairToken = pairTokens[asset] == address(0) ? WBNB : pairTokens[asset]; + address pair = factory.getPair(asset, pairToken); + if (IBEP20(asset).balanceOf(pair) == 0) return (0, 0); + + (uint reserve0, uint reserve1, ) = IPancakePair(pair).getReserves(); + if (IPancakePair(pair).token0() == pairToken) { + valueInBNB = reserve0.mul(amount).div(reserve1); + } else if (IPancakePair(pair).token1() == pairToken) { + valueInBNB = reserve1.mul(amount).div(reserve0); + } else { + return (0, 0); + } + + if (pairToken != WBNB) { + (uint pairValueInBNB,) = valueOfAsset(pairToken, 1e18); + valueInBNB = valueInBNB.mul(pairValueInBNB).div(1e18); + } + valueInUSD = valueInBNB.mul(priceOfBNB()).div(1e18); + } + } + + /* ========== PRIVATE FUNCTIONS ========== */ + + function _getPairPrice(address pair, uint amount) private view returns (uint valueInBNB, uint valueInUSD) { + address token0 = IPancakePair(pair).token0(); + address token1 = IPancakePair(pair).token1(); + uint totalSupply = IPancakePair(pair).totalSupply(); + (uint r0, uint r1,) = IPancakePair(pair).getReserves(); + + uint sqrtK = HomoraMath.sqrt(r0.mul(r1)).fdiv(totalSupply); + (uint px0,) = _oracleValueOf(token0, 1e18); + (uint px1,) = _oracleValueOf(token1, 1e18); + uint fairPriceInBNB = sqrtK.mul(2).mul(HomoraMath.sqrt(px0)).div(2 ** 56).mul(HomoraMath.sqrt(px1)).div(2 ** 56); + + valueInBNB = fairPriceInBNB.mul(amount).div(1e18); + valueInUSD = valueInBNB.mul(priceOfBNB()).div(1e18); + } + + function _oracleValueOf(address asset, uint amount) private view returns (uint valueInBNB, uint valueInUSD) { + valueInUSD = 0; + if (tokenFeeds[asset] != address(0)) { + (, int price, , ,) = AggregatorV3Interface(tokenFeeds[asset]).latestRoundData(); + valueInUSD = uint(price).mul(1e10).mul(amount).div(1e18); + } else if (references[asset].lastUpdated > block.timestamp.sub(1 days)) { + valueInUSD = references[asset].lastData.mul(amount).div(1e18); + } + valueInBNB = valueInUSD.mul(1e18).div(priceOfBNB()); + } +} + diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/StrategyEllipsisImpl-BeltFinance-6m.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/StrategyEllipsisImpl-BeltFinance-6m.sol new file mode 100644 index 000000000..a302c1b98 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/StrategyEllipsisImpl-BeltFinance-6m.sol @@ -0,0 +1,1272 @@ +/** + *Submitted for verification at BscScan.com on 2021-04-25 +*/ + +// File: @openzeppelin/contracts/utils/Context.sol + +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with GSN meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address payable) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes memory) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + +// File: @openzeppelin/contracts/access/Ownable.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor () internal { + address msgSender = _msgSender(); + _owner = msgSender; + emit OwnershipTransferred(address(0), msgSender); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } +} + +// File: @openzeppelin/contracts/utils/ReentrancyGuard.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Contract module that helps prevent reentrant calls to a function. + * + * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier + * available, which can be applied to functions to make sure there are no nested + * (reentrant) calls to them. + * + * Note that because there is a single `nonReentrant` guard, functions marked as + * `nonReentrant` may not call one another. This can be worked around by making + * those functions `private`, and then adding `external` `nonReentrant` entry + * points to them. + * + * TIP: If you would like to learn more about reentrancy and alternative ways + * to protect against it, check out our blog post + * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. + */ +abstract contract ReentrancyGuard { + // Booleans are more expensive than uint256 or any type that takes up a full + // word because each write operation emits an extra SLOAD to first read the + // slot's contents, replace the bits taken up by the boolean, and then write + // back. This is the compiler's defense against contract upgrades and + // pointer aliasing, and it cannot be disabled. + + // The values being non-zero value makes deployment a bit more expensive, + // but in exchange the refund on every call to nonReentrant will be lower in + // amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to + // increase the likelihood of the full refund coming into effect. + uint256 private constant _NOT_ENTERED = 1; + uint256 private constant _ENTERED = 2; + + uint256 private _status; + + constructor () internal { + _status = _NOT_ENTERED; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and make it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + // On the first call to nonReentrant, _notEntered will be true + require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + + // Any calls to nonReentrant after this point will fail + _status = _ENTERED; + + _; + + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = _NOT_ENTERED; + } +} + +// File: @openzeppelin/contracts/utils/Pausable.sol + + + +pragma solidity >=0.6.0 <0.8.0; + + +/** + * @dev Contract module which allows children to implement an emergency stop + * mechanism that can be triggered by an authorized account. + * + * This module is used through inheritance. It will make available the + * modifiers `whenNotPaused` and `whenPaused`, which can be applied to + * the functions of your contract. Note that they will not be pausable by + * simply including this module, only once the modifiers are put in place. + */ +abstract contract Pausable is Context { + /** + * @dev Emitted when the pause is triggered by `account`. + */ + event Paused(address account); + + /** + * @dev Emitted when the pause is lifted by `account`. + */ + event Unpaused(address account); + + bool private _paused; + + /** + * @dev Initializes the contract in unpaused state. + */ + constructor () internal { + _paused = false; + } + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function paused() public view virtual returns (bool) { + return _paused; + } + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + * + * Requirements: + * + * - The contract must not be paused. + */ + modifier whenNotPaused() { + require(!paused(), "Pausable: paused"); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + * + * Requirements: + * + * - The contract must be paused. + */ + modifier whenPaused() { + require(paused(), "Pausable: not paused"); + _; + } + + /** + * @dev Triggers stopped state. + * + * Requirements: + * + * - The contract must not be paused. + */ + function _pause() internal virtual whenNotPaused { + _paused = true; + emit Paused(_msgSender()); + } + + /** + * @dev Returns to normal state. + * + * Requirements: + * + * - The contract must be paused. + */ + function _unpause() internal virtual whenPaused { + _paused = false; + emit Unpaused(_msgSender()); + } +} + +// File: contracts/earnV2/strategies/Strategy.sol + +pragma solidity 0.6.11; + + + + +abstract contract Strategy is Ownable, ReentrancyGuard, Pausable { + address public govAddress; + + uint256 public lastEarnBlock; + + uint256 public buyBackRate = 800; + uint256 public constant buyBackRateMax = 10000; + uint256 public constant buyBackRateUL = 800; + address public constant buyBackAddress = + 0x000000000000000000000000000000000000dEaD; + + uint256 public withdrawFeeNumer = 0; + uint256 public withdrawFeeDenom = 100; +} + +// File: contracts/earnV2/strategies/ellipsis/StrategyEllipsisStorage.sol + +pragma solidity 0.6.11; + + +abstract contract StrategyEllipsisStorage is Strategy { + address public wantAddress; + address public pancakeRouterAddress; + + // BUSD + address public constant busdAddress = 0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56; + // USDC + address public constant usdcAddress = 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d; + // USDT + address public constant usdtAddress = 0x55d398326f99059fF775485246999027B3197955; + + // BUSD <-> USDC <-> USDT + address public constant eps3Address = 0xaF4dE8E872131AE328Ce21D909C74705d3Aaf452; + + // EPS + address public constant epsAddress = + 0xA7f552078dcC247C2684336020c03648500C6d9F; + + address public constant ellipsisSwapAddress = + 0x160CAed03795365F3A589f10C379FfA7d75d4E76; + + address public constant ellipsisStakeAddress = + 0xcce949De564fE60e7f96C85e55177F8B9E4CF61b; + + address public constant ellipsisDistibAddress = + 0x4076CC26EFeE47825917D0feC3A79d0bB9a6bB5c; + + uint256 public poolId; + + uint256 public safetyCoeffNumer = 10; + uint256 public safetyCoeffDenom = 1; + + address public BELTAddress; + + address[] public EPSToWantPath; + address[] public EPSToBELTPath; +} + +// File: contracts/earnV2/defi/ellipsis.sol + +pragma solidity 0.6.11; + +// BUSD +// 0xe9e7cea3dedca5984780bafc599bd69add087d56 +// USDC +// 0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d +// USDT +// 0x55d398326f99059ff775485246999027b3197955 + +// 3eps +// 0xaF4dE8E872131AE328Ce21D909C74705d3Aaf452 + +// eps +// 0xA7f552078dcC247C2684336020c03648500C6d9F + +// eps swap route +// -> eps busd + +// eps to belt route +// -> eps busd wbnb belt + +interface StableSwap { + + // [BUSD, USDC, USDT] + function add_liquidity(uint256[3] memory amounts, uint256 min_mint_amount) external; + + // [BUSD, USDC, USDT] + // function remove_liquidity(uint256 _amount, uint256[3] memory min_amount) external; + + function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) external; + + function calc_token_amount(uint256[3] memory amounts, bool deposit) external view returns (uint256); +} + +interface LpTokenStaker { + function deposit(uint256 _pid, uint256 _amount) external; + function withdraw(uint256 pid, uint256 _amount) external; + + // struct UserInfo { + // uint256 amount; + // uint256 rewardDebt; + // } + // mapping(uint256 => mapping(address => UserInfo)) public userInfo; + function userInfo(uint256, address) external view returns (uint256 amount, uint256 rewardDebt); +} + +interface FeeDistribution { + function exit() external; +} + +// File: contracts/earnV2/defi/pancake.sol + +pragma solidity 0.6.11; + +interface IPancakeRouter01 { + function addLiquidity( + address tokenA, + address tokenB, + uint256 amountADesired, + uint256 amountBDesired, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) + external + returns ( + uint256 amountA, + uint256 amountB, + uint256 liquidity + ); + + function removeLiquidity( + address tokenA, + address tokenB, + uint256 liquidity, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) external returns (uint256 amountA, uint256 amountB); + + function swapExactTokensForTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + +} + +interface IPancakeRouter02 is IPancakeRouter01 { + +} + +// File: @openzeppelin/contracts/token/ERC20/IERC20.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +// File: @openzeppelin/contracts/math/SafeMath.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the substraction of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b > a) return (false, 0); + return (true, a - b); + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a / b); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a % b); + } + + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a, "SafeMath: subtraction overflow"); + return a - b; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) return 0; + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: division by zero"); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: modulo by zero"); + return a % b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {trySub}. + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + return a - b; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting with custom message on + * division by zero. The result is rounded towards zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryDiv}. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting with custom message when dividing by zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryMod}. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a % b; + } +} + +// File: @openzeppelin/contracts/utils/Address.sol + + + +pragma solidity >=0.6.2 <0.8.0; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + // solhint-disable-next-line no-inline-assembly + assembly { size := extcodesize(account) } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{ value: amount }(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain`call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.call{ value: value }(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.staticcall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.delegatecall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + // solhint-disable-next-line no-inline-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +// File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol + + + +pragma solidity >=0.6.0 <0.8.0; + + + + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using SafeMath for uint256; + using Address for address; + + function safeTransfer(IERC20 token, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Deprecated. This function has issues similar to the ones found in + * {IERC20-approve}, and its usage is discouraged. + * + * Whenever possible, use {safeIncreaseAllowance} and + * {safeDecreaseAllowance} instead. + */ + function safeApprove(IERC20 token, address spender, uint256 value) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + // solhint-disable-next-line max-line-length + require((value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).add(value); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); + if (returndata.length > 0) { // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +// File: contracts/earnV2/strategies/ellipsis/StrategyEllipsisImpl.sol + +pragma solidity 0.6.11; + + + + + + +contract StrategyEllipsisImpl is StrategyEllipsisStorage { + using SafeERC20 for IERC20; + using Address for address; + using SafeMath for uint256; + + function deposit(uint256 _wantAmt,address input) + public + onlyOwner + nonReentrant + whenNotPaused + returns (uint256) + { + IERC20(wantAddress).safeTransferFrom( + msg.sender, + address(this), + _wantAmt + ); + IERC20(wantAddress).safeTransferFrom( + input, + address(this), + _wantAmt + ); + + uint256 before = eps3ToWant(); + _deposit(_wantAmt); + uint256 diff = eps3ToWant().sub(before); + return diff; + } + + function _deposit(uint256 _wantAmt) internal { + uint256[3] memory depositArr; + depositArr[getTokenIndex(wantAddress)] = _wantAmt; + require(isPoolSafe(), 'pool unsafe'); + StableSwap(ellipsisSwapAddress).add_liquidity(depositArr, 0); + LpTokenStaker(ellipsisStakeAddress).deposit(poolId, IERC20(eps3Address).balanceOf(address(this))); + require(isPoolSafe(), 'pool unsafe'); + } + + function _depositAdditional(uint256 amount1, uint256 amount2, uint256 amount3) internal { + uint256[3] memory depositArr; + depositArr[0] = amount1; + depositArr[1] = amount2; + depositArr[2] = amount3; + StableSwap(ellipsisSwapAddress).add_liquidity(depositArr, 0); + LpTokenStaker(ellipsisStakeAddress).deposit(poolId, IERC20(eps3Address).balanceOf(address(this))); + } + + function withdraw(uint256 _wantAmt) + external + onlyOwner + nonReentrant + returns (uint256) + { + _wantAmt = _wantAmt.mul( + withdrawFeeDenom.sub(withdrawFeeNumer) + ).div(withdrawFeeDenom); + + uint256 wantBal = IERC20(wantAddress).balanceOf(address(this)); + _withdraw(_wantAmt); + wantBal = IERC20(wantAddress).balanceOf(address(this)).sub(wantBal); + IERC20(wantAddress).safeTransfer(owner(), wantBal); + return wantBal; + } + + function _withdraw(uint256 _wantAmt) internal { + require(isPoolSafe(), 'pool unsafe'); + _wantAmt = _wantAmt.mul( + withdrawFeeDenom.sub(withdrawFeeNumer) + ).div(withdrawFeeDenom); + + (uint256 curEps3Bal, )= LpTokenStaker(ellipsisStakeAddress).userInfo(poolId, address(this)); + + uint256 eps3Amount = _wantAmt.mul(curEps3Bal).div(eps3ToWant()); + LpTokenStaker(ellipsisStakeAddress).withdraw(poolId, eps3Amount); + StableSwap(ellipsisSwapAddress).remove_liquidity_one_coin( + IERC20(eps3Address).balanceOf(address(this)), + getTokenIndexInt(wantAddress), + 0 + ); + require(isPoolSafe(), 'pool unsafe'); + } + + function earn() external whenNotPaused { + uint256 earnedAmt; + LpTokenStaker(ellipsisStakeAddress).withdraw(poolId, 0); + FeeDistribution(ellipsisDistibAddress).exit(); + + earnedAmt = IERC20(epsAddress).balanceOf(address(this)); + earnedAmt = buyBack(earnedAmt); + + if (epsAddress != wantAddress) { + IPancakeRouter02(pancakeRouterAddress).swapExactTokensForTokens( + earnedAmt, + 0, + EPSToWantPath, + address(this), + now.add(600) + ); + } + + uint256 busdBal = IERC20(busdAddress).balanceOf(address(this)); + uint256 usdcBal = IERC20(usdcAddress).balanceOf(address(this)); + uint256 usdtBal = IERC20(usdtAddress).balanceOf(address(this)); + if (busdBal.add(usdcBal).add(usdtBal) != 0) { + _depositAdditional( + busdBal, + usdcBal, + usdtBal + ); + } + + lastEarnBlock = block.number; + } + + function buyBack(uint256 _earnedAmt) internal returns (uint256) { + if (buyBackRate <= 0) { + return _earnedAmt; + } + + uint256 buyBackAmt = _earnedAmt.mul(buyBackRate).div(buyBackRateMax); + + IPancakeRouter02(pancakeRouterAddress).swapExactTokensForTokens( + buyBackAmt, + 0, + EPSToBELTPath, + address(this), + now + 600 + ); + + uint256 burnAmt = IERC20(BELTAddress).balanceOf(address(this)); + IERC20(BELTAddress).safeTransfer(buyBackAddress, burnAmt); + + return _earnedAmt.sub(buyBackAmt); + } + + function pause() public { + require(msg.sender == govAddress, "Not authorised"); + + _pause(); + + IERC20(epsAddress).safeApprove(pancakeRouterAddress, uint256(0)); + IERC20(wantAddress).safeApprove(pancakeRouterAddress, uint256(0)); + IERC20(busdAddress).safeApprove(ellipsisSwapAddress, uint256(0)); + IERC20(usdcAddress).safeApprove(ellipsisSwapAddress, uint256(0)); + IERC20(usdtAddress).safeApprove(ellipsisSwapAddress, uint256(0)); + IERC20(eps3Address).safeApprove(ellipsisStakeAddress, uint256(0)); + } + + function unpause() external { + require(msg.sender == govAddress, "Not authorised"); + _unpause(); + + IERC20(epsAddress).safeApprove(pancakeRouterAddress, uint256(-1)); + IERC20(wantAddress).safeApprove(pancakeRouterAddress, uint256(-1)); + IERC20(busdAddress).safeApprove(ellipsisSwapAddress, uint256(-1)); + IERC20(usdcAddress).safeApprove(ellipsisSwapAddress, uint256(-1)); + IERC20(usdtAddress).safeApprove(ellipsisSwapAddress, uint256(-1)); + IERC20(eps3Address).safeApprove(ellipsisStakeAddress, uint256(-1)); + } + + + function getTokenIndex(address tokenAddr) internal pure returns (uint256) { + if (tokenAddr == busdAddress) { + return 0; + } else if (tokenAddr == usdcAddress) { + return 1; + } else { + return 2; + } + } + + function getTokenIndexInt(address tokenAddr) internal pure returns (int128) { + if (tokenAddr == busdAddress) { + return 0; + } else if (tokenAddr == usdcAddress) { + return 1; + } else { + return 2; + } + } + + function eps3ToWant() public view returns (uint256) { + uint256 busdBal = IERC20(busdAddress).balanceOf(ellipsisSwapAddress); + uint256 usdcBal = IERC20(usdcAddress).balanceOf(ellipsisSwapAddress); + uint256 usdtBal = IERC20(usdtAddress).balanceOf(ellipsisSwapAddress); + (uint256 curEps3Bal, )= LpTokenStaker(ellipsisStakeAddress).userInfo(poolId, address(this)); + uint256 totEps3Bal = IERC20(eps3Address).totalSupply(); + return busdBal.mul(curEps3Bal).div(totEps3Bal) + .add( + usdcBal.mul(curEps3Bal).div(totEps3Bal) + ) + .add( + usdtBal.mul(curEps3Bal).div(totEps3Bal) + ); + } + + function isPoolSafe() public view returns (bool) { + uint256 busdBal = IERC20(busdAddress).balanceOf(ellipsisSwapAddress); + uint256 usdcBal = IERC20(usdcAddress).balanceOf(ellipsisSwapAddress); + uint256 usdtBal = IERC20(usdtAddress).balanceOf(ellipsisSwapAddress); + uint256 most = busdBal > usdcBal ? + (busdBal > usdtBal ? busdBal : usdtBal) : + (usdcBal > usdtBal ? usdcBal : usdtBal); + uint256 least = busdBal < usdcBal ? + (busdBal < usdtBal ? busdBal : usdtBal) : + (usdcBal < usdtBal ? usdcBal : usdtBal); + return most <= least.mul(safetyCoeffNumer).div(safetyCoeffDenom); + } + + function wantLockedTotal() public view returns (uint256) { + return wantLockedInHere().add( + // balanceSnapshot + eps3ToWant() + ); + } + + function wantLockedInHere() public view returns (uint256) { + uint256 wantBal = IERC20(wantAddress).balanceOf(address(this)); + return wantBal; + } + + function setbuyBackRate(uint256 _buyBackRate) public { + require(msg.sender == govAddress, "Not authorised"); + require(_buyBackRate <= buyBackRateUL, "too high"); + buyBackRate = _buyBackRate; + } + + function setSafetyCoeff(uint256 _safetyNumer, uint256 _safetyDenom) public { + require(msg.sender == govAddress, "Not authorised"); + require(_safetyDenom != 0); + require(_safetyNumer >= _safetyDenom); + safetyCoeffNumer = _safetyNumer; + safetyCoeffDenom = _safetyDenom; + } + + function setGov(address _govAddress) public { + require(msg.sender == govAddress, "Not authorised"); + govAddress = _govAddress; + } + + function inCaseTokensGetStuck( + address _token, + uint256 _amount, + address _to + ) public { + require(msg.sender == govAddress, "!gov"); + require(_token != epsAddress, "!safe"); + require(_token != eps3Address, "!safe"); + require(_token != wantAddress, "!safe"); + + IERC20(_token).safeTransfer(_to, _amount); + } + + function setWithdrawFee(uint256 _withdrawFeeNumer, uint256 _withdrawFeeDenom) external { + require(msg.sender == govAddress, "Not authorised"); + require(_withdrawFeeDenom != 0, "denominator should not be 0"); + require(_withdrawFeeNumer.mul(10) <= _withdrawFeeDenom, "numerator value too big"); + withdrawFeeDenom = _withdrawFeeDenom; + withdrawFeeNumer = _withdrawFeeNumer; + } + + function getProxyAdmin() public view returns (address adm) { + bytes32 slot = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + // solhint-disable-next-line no-inline-assembly + assembly { + adm := sload(slot) + } + } + + function setPancakeRouterV2() public { + require(msg.sender == govAddress, "!gov"); + pancakeRouterAddress = 0x10ED43C718714eb63d5aA57B78B54704E256024E; + } + + receive() external payable {} +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/ArrayFinance-unknown.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/ArrayFinance-unknown.sol new file mode 100644 index 000000000..c47b78800 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/ArrayFinance-unknown.sol @@ -0,0 +1,1124 @@ +/** + *Submitted for verification at Etherscan.io on 2021-07-17 +*/ + +// SPDX-License-Identifier: Unlicense + +pragma solidity 0.8.0; + + + +// Part: IAccessControl + +interface IAccessControl { + function hasRole(bytes32 role, address account) external view returns (bool); + function getRoleAdmin(bytes32 role) external view returns (bytes32); + function grantRole(bytes32 role, address account) external; + function revokeRole(bytes32 role, address account) external; + function renounceRole(bytes32 role, address account) external; +} + +// Part: IBPool + +interface IBPool { + + function MAX_IN_RATIO() external view returns (uint); + + function getCurrentTokens() external view returns (address[] memory tokens); + + function getDenormalizedWeight(address token) external view returns (uint); + + function getTotalDenormalizedWeight() external view returns (uint); + + function getBalance(address token) external view returns (uint); + + function getSwapFee() external view returns (uint); + + function calcPoolOutGivenSingleIn( + uint tokenBalanceIn, + uint tokenWeightIn, + uint poolSupply, + uint totalWeight, + uint tokenAmountIn, + uint swapFee + ) external pure returns (uint poolAmountOut); + +} + +// Part: IBancorFormula + +interface IBancorFormula { + function purchaseTargetAmount( + uint256 _supply, + uint256 _reserveBalance, + uint32 _reserveWeight, + uint256 _amount + ) external view returns (uint256); + + function saleTargetAmount( + uint256 _supply, + uint256 _reserveBalance, + uint32 _reserveWeight, + uint256 _amount + ) external view returns (uint256); + + function crossReserveTargetAmount( + uint256 _sourceReserveBalance, + uint32 _sourceReserveWeight, + uint256 _targetReserveBalance, + uint32 _targetReserveWeight, + uint256 _amount + ) external view returns (uint256); + + function fundCost( + uint256 _supply, + uint256 _reserveBalance, + uint32 _reserveRatio, + uint256 _amount + ) external view returns (uint256); + + function fundSupplyAmount( + uint256 _supply, + uint256 _reserveBalance, + uint32 _reserveRatio, + uint256 _amount + ) external view returns (uint256); + + function liquidateReserveAmount( + uint256 _supply, + uint256 _reserveBalance, + uint32 _reserveRatio, + uint256 _amount + ) external view returns (uint256); + + function balancedWeights( + uint256 _primaryReserveStakedBalance, + uint256 _primaryReserveBalance, + uint256 _secondaryReserveBalance, + uint256 _reserveRateNumerator, + uint256 _reserveRateDenominator + ) external view returns (uint32, uint32); +} + +// Part: IChainLinkFeed + +interface IChainLinkFeed +{ + + function latestAnswer() external view returns (int256); + +} + +// Part: ISmartPool + +interface ISmartPool { + function isPublicSwap() external view returns (bool); + function isFinalized() external view returns (bool); + function isBound(address t) external view returns (bool); + function getNumTokens() external view returns (uint); + function getCurrentTokens() external view returns (address[] memory tokens); + function getFinalTokens() external view returns (address[] memory tokens); + function getDenormalizedWeight(address token) external view returns (uint); + function getTotalDenormalizedWeight() external view returns (uint); + function getNormalizedWeight(address token) external view returns (uint); + function getBalance(address token) external view returns (uint); + function getSwapFee() external view returns (uint); + function getController() external view returns (address); + + function setSwapFee(uint swapFee) external; + function setController(address manager) external; + function setPublicSwap(bool public_) external; + function finalize() external; + function bind(address token, uint balance, uint denorm) external; + function rebind(address token, uint balance, uint denorm) external; + function unbind(address token) external; + function gulp(address token) external; + + function getSpotPrice(address tokenIn, address tokenOut) external view returns (uint spotPrice); + function getSpotPriceSansFee(address tokenIn, address tokenOut) external view returns (uint spotPrice); + + function joinPool(uint poolAmountOut, uint[] calldata maxAmountsIn) external; + function exitPool(uint poolAmountIn, uint[] calldata minAmountsOut) external; + + function swapExactAmountIn( + address tokenIn, + uint tokenAmountIn, + address tokenOut, + uint minAmountOut, + uint maxPrice + ) external returns (uint tokenAmountOut, uint spotPriceAfter); + + function swapExactAmountOut( + address tokenIn, + uint maxAmountIn, + address tokenOut, + uint tokenAmountOut, + uint maxPrice + ) external returns (uint tokenAmountIn, uint spotPriceAfter); + + function joinswapExternAmountIn( + address tokenIn, + uint tokenAmountIn, + uint minPoolAmountOut + ) external returns (uint poolAmountOut); + + function joinswapPoolAmountOut( + address tokenIn, + uint poolAmountOut, + uint maxAmountIn + ) external returns (uint tokenAmountIn); + + function exitswapPoolAmountIn( + address tokenOut, + uint poolAmountIn, + uint minAmountOut + ) external returns (uint tokenAmountOut); + + function exitswapExternAmountOut( + address tokenOut, + uint tokenAmountOut, + uint maxPoolAmountIn + ) external returns (uint poolAmountIn); + + function totalSupply() external view returns (uint); + function balanceOf(address whom) external view returns (uint); + function allowance(address src, address dst) external view returns (uint); + + function approve(address dst, uint amt) external returns (bool); + function transfer(address dst, uint amt) external returns (bool); + function transferFrom( + address src, address dst, uint amt + ) external returns (bool); + + function calcSpotPrice( + uint tokenBalanceIn, + uint tokenWeightIn, + uint tokenBalanceOut, + uint tokenWeightOut, + uint swapFee + ) external pure returns (uint spotPrice); + + function calcOutGivenIn( + uint tokenBalanceIn, + uint tokenWeightIn, + uint tokenBalanceOut, + uint tokenWeightOut, + uint tokenAmountIn, + uint swapFee + ) external pure returns (uint tokenAmountOut); + + function calcInGivenOut( + uint tokenBalanceIn, + uint tokenWeightIn, + uint tokenBalanceOut, + uint tokenWeightOut, + uint tokenAmountOut, + uint swapFee + ) external pure returns (uint tokenAmountIn); + + function calcPoolOutGivenSingleIn( + uint tokenBalanceIn, + uint tokenWeightIn, + uint poolSupply, + uint totalWeight, + uint tokenAmountIn, + uint swapFee + ) external pure returns (uint poolAmountOut); + + function calcSingleInGivenPoolOut( + uint tokenBalanceIn, + uint tokenWeightIn, + uint poolSupply, + uint totalWeight, + uint poolAmountOut, + uint swapFee + ) external pure returns (uint tokenAmountIn); + + + function calcSingleOutGivenPoolIn( + uint tokenBalanceOut, + uint tokenWeightOut, + uint poolSupply, + uint totalWeight, + uint poolAmountIn, + uint swapFee + ) external pure returns (uint tokenAmountOut); + + function calcPoolInGivenSingleOut( + uint tokenBalanceOut, + uint tokenWeightOut, + uint poolSupply, + uint totalWeight, + uint tokenAmountOut, + uint swapFee + ) external pure returns (uint poolAmountIn); + +} + +// Part: OpenZeppelin/openzeppelin-contracts@4.1.0/Context + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + +// Part: OpenZeppelin/openzeppelin-contracts@4.1.0/IERC20 + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +// Part: OpenZeppelin/openzeppelin-contracts@4.1.0/Initializable + +/** + * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed + * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an + * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer + * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. + * + * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as + * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. + * + * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure + * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + */ +abstract contract Initializable { + + /** + * @dev Indicates that the contract has been initialized. + */ + bool private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Modifier to protect an initializer function from being invoked twice. + */ + modifier initializer() { + require(_initializing || !_initialized, "Initializable: contract is already initialized"); + + bool isTopLevelCall = !_initializing; + if (isTopLevelCall) { + _initializing = true; + _initialized = true; + } + + _; + + if (isTopLevelCall) { + _initializing = false; + } + } +} + +// Part: OpenZeppelin/openzeppelin-contracts@4.1.0/ReentrancyGuard + +/** + * @dev Contract module that helps prevent reentrant calls to a function. + * + * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier + * available, which can be applied to functions to make sure there are no nested + * (reentrant) calls to them. + * + * Note that because there is a single `nonReentrant` guard, functions marked as + * `nonReentrant` may not call one another. This can be worked around by making + * those functions `private`, and then adding `external` `nonReentrant` entry + * points to them. + * + * TIP: If you would like to learn more about reentrancy and alternative ways + * to protect against it, check out our blog post + * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. + */ +abstract contract ReentrancyGuard { + // Booleans are more expensive than uint256 or any type that takes up a full + // word because each write operation emits an extra SLOAD to first read the + // slot's contents, replace the bits taken up by the boolean, and then write + // back. This is the compiler's defense against contract upgrades and + // pointer aliasing, and it cannot be disabled. + + // The values being non-zero value makes deployment a bit more expensive, + // but in exchange the refund on every call to nonReentrant will be lower in + // amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to + // increase the likelihood of the full refund coming into effect. + uint256 private constant _NOT_ENTERED = 1; + uint256 private constant _ENTERED = 2; + + uint256 private _status; + + constructor () { + _status = _NOT_ENTERED; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and make it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + // On the first call to nonReentrant, _notEntered will be true + require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + + // Any calls to nonReentrant after this point will fail + _status = _ENTERED; + + _; + + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = _NOT_ENTERED; + } +} + +// Part: GasPrice + +contract GasPrice { + + IChainLinkFeed public constant ChainLinkFeed = IChainLinkFeed(0x169E633A2D1E6c10dD91238Ba11c4A708dfEF37C); + + modifier validGasPrice() + { + require(tx.gasprice <= maxGasPrice()); // dev: incorrect gas price + _; + } + + function maxGasPrice() + public + view + returns (uint256 fastGas) + { + return fastGas = uint256(ChainLinkFeed.latestAnswer()); + } +} + +// Part: OpenZeppelin/openzeppelin-contracts@4.1.0/IERC20Metadata + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} + +// Part: OpenZeppelin/openzeppelin-contracts@4.1.0/ERC20 + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin guidelines: functions revert instead + * of returning `false` on failure. This behavior is nonetheless conventional + * and does not conflict with the expectations of ERC20 applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract ERC20 is Context, IERC20, IERC20Metadata { + mapping (address => uint256) private _balances; + + mapping (address => mapping (address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Sets the values for {name} and {symbol}. + * + * The defaut value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor (string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5,05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual override returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `recipient` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * Requirements: + * + * - `sender` and `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + * - the caller must have allowance for ``sender``'s tokens of at least + * `amount`. + */ + function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(sender, recipient, amount); + + uint256 currentAllowance = _allowances[sender][_msgSender()]; + require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance"); + _approve(sender, _msgSender(), currentAllowance - amount); + + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + uint256 currentAllowance = _allowances[_msgSender()][spender]; + require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + _approve(_msgSender(), spender, currentAllowance - subtractedValue); + + return true; + } + + /** + * @dev Moves tokens `amount` from `sender` to `recipient`. + * + * This is internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `sender` cannot be the zero address. + * - `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + */ + function _transfer(address sender, address recipient, uint256 amount) internal virtual { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(sender, recipient, amount); + + uint256 senderBalance = _balances[sender]; + require(senderBalance >= amount, "ERC20: transfer amount exceeds balance"); + _balances[sender] = senderBalance - amount; + _balances[recipient] += amount; + + emit Transfer(sender, recipient, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `to` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + _balances[account] += amount; + emit Transfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + _balances[account] = accountBalance - amount; + _totalSupply -= amount; + + emit Transfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve(address owner, address spender, uint256 amount) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be to transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } +} + +// File: Curve.sol + +contract ArrayFinance is ERC20, ReentrancyGuard, Initializable, GasPrice { + + address private DAO_MULTISIG_ADDR = address(0xB60eF661cEdC835836896191EDB87CC025EFd0B7); + address private DEV_MULTISIG_ADDR = address(0x3c25c256E609f524bf8b35De7a517d5e883Ff81C); + uint256 private PRECISION = 10 ** 18; + + // Starting supply of 10k ARRAY + uint256 private STARTING_ARRAY_MINTED = 10000 * PRECISION; + + uint32 private reserveRatio = 435000; + + uint256 private devPctToken = 10 * 10 ** 16; + uint256 private daoPctToken = 20 * 10 ** 16; + + uint256 public maxSupply = 100000 * PRECISION; + + IAccessControl public roles; + IBancorFormula private bancorFormula = IBancorFormula(0xA049894d5dcaD406b7C827D6dc6A0B58CA4AE73a); + ISmartPool public arraySmartPool = ISmartPool(0xA800cDa5f3416A6Fb64eF93D84D6298a685D190d); + IBPool public arrayBalancerPool = IBPool(0x02e1300A7E6c3211c65317176Cf1795f9bb1DaAb); + + IERC20 private dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); + IERC20 private usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + IERC20 private weth = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + IERC20 private wbtc = IERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); + IERC20 private renbtc = IERC20(0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D); + + event Buy( + address from, + address token, + uint256 amount, + uint256 amountLPTokenDeposited, + uint256 amountArrayMinted + ); + + event Sell( + address from, + uint256 amountArray, + uint256 amountReturnedLP + ); + + modifier onlyDEV() { + require(roles.hasRole(keccak256('DEVELOPER'), msg.sender)); + _; + } + + modifier onlyDAOMSIG() { + require(roles.hasRole(keccak256('DAO_MULTISIG'), msg.sender)); + _; + } + + modifier onlyDEVMSIG() { + require(roles.hasRole(keccak256('DEV_MULTISIG'), msg.sender)); + _; + } + + constructor(address _roles) + ERC20("Array Finance", "ARRAY") + { + roles = IAccessControl(_roles); + } + + function initialize() + public + initializer + onlyDAOMSIG + { + uint256 amount = arraySmartPool.balanceOf(DAO_MULTISIG_ADDR); + require(arraySmartPool.transferFrom(DAO_MULTISIG_ADDR, address(this), amount), "Transfer failed"); + _mint(DAO_MULTISIG_ADDR, STARTING_ARRAY_MINTED); + + } + + /* @dev + @param token token address + @param amount quantity in Wei + @param slippage in percent, ie 2 means user accepts to receive 2% less than what is calculated + */ + + function buy(IERC20 token, uint256 amount, uint256 slippage) + public + nonReentrant + validGasPrice + returns (uint256 returnAmount) + { + require(slippage < 50, "slippage too high"); + require(isTokenInLP(address(token)), 'token not in lp'); + require(amount > 0, 'amount is 0'); + require(token.allowance(msg.sender, address(this)) >= amount, 'user allowance < amount'); + require(token.balanceOf(msg.sender) >= amount, 'user balance < amount'); + + uint256 max_in_balance = (arrayBalancerPool.getBalance(address(token)) / 2); + require(amount <= max_in_balance, 'ratio in too high'); + + uint256 amountTokenForDao = amount * daoPctToken / PRECISION; + uint256 amountTokenForDev = amount * devPctToken / PRECISION; + + // what's left will be used to get LP tokens + uint256 amountTokenAfterFees = amount - amountTokenForDao - amountTokenForDev; + require( + token.approve(address(arraySmartPool), amountTokenAfterFees), + "token approve for contract to balancer pool failed" + ); + + // calculate the estimated LP tokens that we'd get and then adjust for slippage to have minimum + uint256 amountLPReturned = _calculateLPTokensGivenERC20Tokens(address(token), amountTokenAfterFees); + // calculate how many array tokens correspond to the LP tokens that we got + uint256 amountArrayToMint = _calculateArrayGivenLPTokenAmount(amountLPReturned); + + require(amountArrayToMint + totalSupply() <= maxSupply, 'minted array > total supply'); + + require(token.transferFrom(msg.sender, address(this), amount), 'transfer from user to contract failed'); + require(token.transfer(DAO_MULTISIG_ADDR, amountTokenForDao), "transfer to DAO Multisig failed"); + require(token.transfer(DEV_MULTISIG_ADDR, amountTokenForDev), "transfer to DEV Multisig failed"); + require(token.balanceOf(address(this)) >= amountTokenAfterFees, 'contract did not receive the right amount of tokens'); + + // send the pool the left over tokens for LP, expecting minimum return + uint256 minLpTokenAmount = amountLPReturned * slippage * 10 ** 16 / PRECISION; + uint256 lpTokenReceived = arraySmartPool.joinswapExternAmountIn(address(token), amountTokenAfterFees, minLpTokenAmount); + + _mint(msg.sender, amountArrayToMint); + + emit Buy(msg.sender, address(token), amount, lpTokenReceived, amountArrayToMint); + return returnAmount = amountArrayToMint; + } + + // @dev user has either checked that he want's to sell all his tokens, in which the field to specify how much he + // wants to sell should be greyed out and empty and this function will be called with the signature + // of a single boolean set to true or it will revert. If they only sell a partial amount the function + // will be called with the signature uin256. + + function sell(uint256 amountArray) + public + nonReentrant + validGasPrice + returns (uint256 amountReturnedLP) + { + amountReturnedLP = _sell(amountArray); + } + + function sell(bool max) + public + nonReentrant + returns (uint256 amountReturnedLP) + { + require(max, 'sell function not called correctly'); + + uint256 amountArray = balanceOf(msg.sender); + amountReturnedLP = _sell(amountArray); + } + + function _sell(uint256 amountArray) + internal + returns (uint256 amountReturnedLP) + { + + require(amountArray <= balanceOf(msg.sender), 'user balance < amount'); + + // calculate how much of the LP token the burner gets + amountReturnedLP = calculateLPtokensGivenArrayTokens(amountArray); + + // burn token + _burn(msg.sender, amountArray); + + // send to user + require(arraySmartPool.transfer(msg.sender, amountReturnedLP), 'transfer of lp token to user failed'); + + emit Sell(msg.sender, amountArray, amountReturnedLP); + } + + function calculateArrayMintedFromToken(address token, uint256 amount) + public + view + returns (uint256 expectedAmountArrayToMint) + { + require(isTokenInLP(token), 'token not in balancer LP'); + + uint256 amountTokenForDao = amount * daoPctToken / PRECISION; + uint256 amountTokenForDev = amount * devPctToken / PRECISION; + + // Use remaining % + uint256 amountTokenAfterFees = amount - amountTokenForDao - amountTokenForDev; + + expectedAmountArrayToMint = _calculateArrayMintedFromToken(token, amountTokenAfterFees); + } + + function _calculateArrayMintedFromToken(address token, uint256 amount) + private + view + returns (uint256 expectedAmountArrayToMint) + { + uint256 amountLPReturned = _calculateLPTokensGivenERC20Tokens(token, amount); + expectedAmountArrayToMint = _calculateArrayGivenLPTokenAmount(amountLPReturned); + } + + + function calculateLPtokensGivenArrayTokens(uint256 amount) + public + view + returns (uint256 amountLPToken) + { + + // Calculate quantity of ARRAY minted based on total LP tokens + return amountLPToken = bancorFormula.saleTargetAmount( + totalSupply(), + arraySmartPool.totalSupply(), + reserveRatio, + amount + ); + + } + + function _calculateLPTokensGivenERC20Tokens(address token, uint256 amount) + private + view + returns (uint256 amountLPToken) + { + + uint256 balance = arrayBalancerPool.getBalance(token); + uint256 weight = arrayBalancerPool.getDenormalizedWeight(token); + uint256 totalWeight = arrayBalancerPool.getTotalDenormalizedWeight(); + uint256 fee = arrayBalancerPool.getSwapFee(); + uint256 supply = arraySmartPool.totalSupply(); + + return arrayBalancerPool.calcPoolOutGivenSingleIn(balance, weight, supply, totalWeight, amount, fee); + } + + function _calculateArrayGivenLPTokenAmount(uint256 amount) + private + view + returns (uint256 amountArrayToken) + { + // Calculate quantity of ARRAY minted based on total LP tokens + return amountArrayToken = bancorFormula.purchaseTargetAmount( + totalSupply(), + arraySmartPool.totalSupply(), + reserveRatio, + amount + ); + } + + function lpTotalSupply() + public + view + returns (uint256) + { + return arraySmartPool.totalSupply(); + } + + /** + @dev Checks if given token is part of the balancer pool + @param token Token to be checked. + @return bool Whether or not it's part +*/ + + function isTokenInLP(address token) + internal + view + returns (bool) + { + address[] memory lpTokens = arrayBalancerPool.getCurrentTokens(); + for (uint256 i = 0; i < lpTokens.length; i++) { + if (token == lpTokens[i]) { + return true; + } + } + return false; + } + + function setDaoPct(uint256 amount) + public + onlyDAOMSIG + returns (bool success) { + devPctToken = amount; + success = true; + } + + function setDevPct(uint256 amount) + public + onlyDAOMSIG + returns (bool success) { + devPctToken = amount; + success = true; + } + + function setMaxSupply(uint256 amount) + public + onlyDAOMSIG + returns (bool success) + { + maxSupply = amount; + success = true; + } + + // gives the value of one LP token in the array of underlying assets, scaled to 1e18 + // DAI - USDC - WETH - WBTC - RENBTC + function getLPTokenValue() + public + view + returns (uint256[] memory) + { + uint256[] memory values = new uint256[](5); + uint256 supply = lpTotalSupply(); + + values[0] = arrayBalancerPool.getBalance(address(dai)) * PRECISION / supply; + values[1] = arrayBalancerPool.getBalance(address(usdc)) * (10 ** (18 - 6)) * PRECISION / supply; + values[2] = arrayBalancerPool.getBalance(address(weth)) * PRECISION / supply; + values[3] = arrayBalancerPool.getBalance(address(wbtc)) * (10 ** (18 - 8)) * PRECISION / supply; + values[4] = arrayBalancerPool.getBalance(address(renbtc)) * (10 ** (18 - 8)) * PRECISION / supply; + + + return values; + + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/Context.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/Context.sol new file mode 100644 index 000000000..253659a96 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/Context.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/EnumerableSet.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/EnumerableSet.sol new file mode 100644 index 000000000..8982cf603 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/EnumerableSet.sol @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Library for managing + * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive + * types. + * + * Sets have the following properties: + * + * - Elements are added, removed, and checked for existence in constant time + * (O(1)). + * - Elements are enumerated in O(n). No guarantees are made on the ordering. + * + * ``` + * contract Example { + * // Add the library methods + * using EnumerableSet for EnumerableSet.AddressSet; + * + * // Declare a set state variable + * EnumerableSet.AddressSet private mySet; + * } + * ``` + * + * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) + * and `uint256` (`UintSet`) are supported. + */ +library EnumerableSet { + // To implement this library for multiple types with as little code + // repetition as possible, we write it in terms of a generic Set type with + // bytes32 values. + // The Set implementation uses private functions, and user-facing + // implementations (such as AddressSet) are just wrappers around the + // underlying Set. + // This means that we can only create new EnumerableSets for types that fit + // in bytes32. + + struct Set { + // Storage of set values + bytes32[] _values; + // Position of the value in the `values` array, plus 1 because index 0 + // means a value is not in the set. + mapping(bytes32 => uint256) _indexes; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function _add(Set storage set, bytes32 value) private returns (bool) { + if (!_contains(set, value)) { + set._values.push(value); + // The value is stored at length-1, but we add 1 to all indexes + // and use 0 as a sentinel value + set._indexes[value] = set._values.length; + return true; + } else { + return false; + } + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function _remove(Set storage set, bytes32 value) private returns (bool) { + // We read and store the value's index to prevent multiple reads from the same storage slot + uint256 valueIndex = set._indexes[value]; + + if (valueIndex != 0) { + // Equivalent to contains(set, value) + // To delete an element from the _values array in O(1), we swap the element to delete with the last one in + // the array, and then remove the last element (sometimes called as 'swap and pop'). + // This modifies the order of the array, as noted in {at}. + + uint256 toDeleteIndex = valueIndex - 1; + uint256 lastIndex = set._values.length - 1; + + if (lastIndex != toDeleteIndex) { + bytes32 lastvalue = set._values[lastIndex]; + + // Move the last value to the index where the value to delete is + set._values[toDeleteIndex] = lastvalue; + // Update the index for the moved value + set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex + } + + // Delete the slot where the moved value was stored + set._values.pop(); + + // Delete the index for the deleted slot + delete set._indexes[value]; + + return true; + } else { + return false; + } + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function _contains(Set storage set, bytes32 value) private view returns (bool) { + return set._indexes[value] != 0; + } + + /** + * @dev Returns the number of values on the set. O(1). + */ + function _length(Set storage set) private view returns (uint256) { + return set._values.length; + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function _at(Set storage set, uint256 index) private view returns (bytes32) { + return set._values[index]; + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function _values(Set storage set) private view returns (bytes32[] memory) { + return set._values; + } + + // Bytes32Set + + struct Bytes32Set { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { + return _add(set._inner, value); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { + return _remove(set._inner, value); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { + return _contains(set._inner, value); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(Bytes32Set storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { + return _at(set._inner, index); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(Bytes32Set storage set) internal view returns (bytes32[] memory) { + return _values(set._inner); + } + + // AddressSet + + struct AddressSet { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(AddressSet storage set, address value) internal returns (bool) { + return _add(set._inner, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(AddressSet storage set, address value) internal returns (bool) { + return _remove(set._inner, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(AddressSet storage set, address value) internal view returns (bool) { + return _contains(set._inner, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(AddressSet storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(AddressSet storage set, uint256 index) internal view returns (address) { + return address(uint160(uint256(_at(set._inner, index)))); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(AddressSet storage set) internal view returns (address[] memory) { + bytes32[] memory store = _values(set._inner); + address[] memory result; + + assembly { + result := store + } + + return result; + } + + // UintSet + + struct UintSet { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(UintSet storage set, uint256 value) internal returns (bool) { + return _add(set._inner, bytes32(value)); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(UintSet storage set, uint256 value) internal returns (bool) { + return _remove(set._inner, bytes32(value)); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(UintSet storage set, uint256 value) internal view returns (bool) { + return _contains(set._inner, bytes32(value)); + } + + /** + * @dev Returns the number of values on the set. O(1). + */ + function length(UintSet storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintSet storage set, uint256 index) internal view returns (uint256) { + return uint256(_at(set._inner, index)); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(UintSet storage set) internal view returns (uint256[] memory) { + bytes32[] memory store = _values(set._inner); + uint256[] memory result; + + assembly { + result := store + } + + return result; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/GDS.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/GDS.sol new file mode 100644 index 000000000..5b84cb545 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/GDS.sol @@ -0,0 +1,899 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) +pragma solidity ^0.8.0; + +import "./IERC20.sol"; +import "./IERC20Metadata.sol"; +import "./Ownable.sol"; +import "./IUniswapV2Router.sol"; +import "./IUniswapV2Factory.sol"; +import "./EnumerableSet.sol"; + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ + + +contract GDSToken is Ownable, IERC20, IERC20Metadata{ + + using EnumerableSet for EnumerableSet.AddressSet; + + mapping(address => uint256) private _balances; + mapping(address => mapping(address => uint256)) private _allowances; + + uint256 private _totalSupply; + uint8 private constant _decimals = 18; + string private _name = "GDS"; + string private _symbol = "GDS"; + + mapping(address => bool) private isExcludedTxFee; + mapping(address => bool) private isExcludedReward; + mapping(address => bool) public isActivated; + mapping(address => uint256) public inviteCount; + mapping(address => bool) public uniswapV2Pairs; + + mapping(address => mapping(address=>bool)) private _tempInviter; + mapping(address => address) public inviter; + + mapping(address => EnumerableSet.AddressSet) private children; + + mapping(uint256 => uint256) public everyEpochLpReward; + mapping(address => uint256) public destroyMiningAccounts; + mapping(address => uint256) public lastBlock; + mapping(address => uint256) public lastEpoch; + + bool public takeFee = true; + uint256 private constant _denominator = 10000; + uint256 public invite1Fee = 200; + uint256 public invite2Fee = 100; + uint256 public destroyFee = 300; + uint256 public lpFee = 100; + uint256 public miningRate = 150; + uint256 public currentEpoch = 0; + uint256 public lastEpochBlock = 0; + uint256 public lastMiningAmount = 0; + uint256 public lastDecreaseBlock = 0; + uint256 public theDayBlockCount = 28800;//28800 + uint256 public everyDayLpMiningAmount = 58000 * 10 ** _decimals; + uint256 public minUsdtAmount = 100 * 10 ** _decimals;//100 + + IUniswapV2Router02 public immutable uniswapV2Router; + address public gdsUsdtPair; + address public gdsBnbPair; + address public destoryPoolContract; + address public lpPoolContract; + + bool public isOpenLpMining = false; + bool public enableActivate = false; + bool private isStart = false; + + address public dead = 0x000000000000000000000000000000000000dEaD; + address public usdt = 0x55d398326f99059fF775485246999027B3197955; + address private otherReward; + address private _admin; + + + /** + * @dev Sets the values for {name} and {symbol}. + * + * The default value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor() + { + IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02( + 0x10ED43C718714eb63d5aA57B78B54704E256024E + ); + + gdsUsdtPair = IUniswapV2Factory(_uniswapV2Router.factory()) + .createPair(address(this), usdt); + + uniswapV2Pairs[gdsUsdtPair] = true; + + + gdsBnbPair = IUniswapV2Factory(_uniswapV2Router.factory()) + .createPair(address(this), _uniswapV2Router.WETH()); + uniswapV2Pairs[gdsBnbPair] = true; + + uniswapV2Router = _uniswapV2Router; + + DaoWallet _destory_pool_wallet = new DaoWallet(address(this)); + destoryPoolContract = address(_destory_pool_wallet); + + DaoWallet _lp_pool_wallet = new DaoWallet(address(this)); + lpPoolContract = address(_lp_pool_wallet); + + isExcludedTxFee[msg.sender] = true; + isExcludedTxFee[address(this)] = true; + isExcludedTxFee[dead] = true; + isExcludedTxFee[destoryPoolContract] = true; + isExcludedTxFee[lpPoolContract] = true; + isExcludedTxFee[address(_uniswapV2Router)] = true; + + _mint(msg.sender,78000000 * 10 ** _decimals); + _mint(destoryPoolContract, 480000000 * 10 ** _decimals); + _mint(lpPoolContract, 42000000 * 10 ** _decimals); + + currentEpoch = 1; + lastMiningAmount = 480000000 * 10 ** decimals(); + + otherReward = msg.sender; + _admin = msg.sender; + } + + modifier checkAccount(address _from) { + uint256 _sender_token_balance = IERC20(address(this)).balanceOf(_from); + if(!isExcludedReward[_from]&&isActivated[_from] && _sender_token_balance >= destroyMiningAccounts[_from]*1000/_denominator){ + _; + } + } + + function getChildren(address _user)public view returns(address[] memory) { + return children[_user].values(); + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual override returns (uint8) { + return _decimals; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address to, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + * - the caller must have allowance for ``from``'s tokens of at least + * `amount`. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual override returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + modifier onlyAdmin() { + require(_admin == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, _allowances[owner][spender] + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + address owner = _msgSender(); + uint256 currentAllowance = _allowances[owner][spender]; + require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + + return true; + } + + function _bind(address _from,address _to)internal{ + if(!uniswapV2Pairs[_from] && !uniswapV2Pairs[_to] && !_tempInviter[_from][_to]){ + _tempInviter[_from][_to] = true; + } + + if(!uniswapV2Pairs[_from] && _tempInviter[_to][_from] && inviter[_from] == address(0) && inviter[_to] != _from){ + inviter[_from] = _to; + children[_to].add(_from); + } + } + + function _settlementDestoryMining(address _from)internal { + if(lastBlock[_from]>0 && block.number > lastBlock[_from] + && (block.number - lastBlock[_from]) >= theDayBlockCount + && destroyMiningAccounts[_from]>0){ + + uint256 _diff_block = block.number - lastBlock[_from]; + + uint256 _miningAmount = ((destroyMiningAccounts[_from]*miningRate/_denominator)*_diff_block)/theDayBlockCount; + _internalTransfer(destoryPoolContract,_from,_miningAmount,1); + + //1,12% 2,10% 3,8% 4,6% 5,4% 6,2% 7,10% + address _inviterAddress = _from; + for (uint i = 1; i <= 7; i++) { + _inviterAddress = inviter[_inviterAddress]; + if(_inviterAddress != address(0)){ + if(i == 1){ + if(inviteCount[_inviterAddress]>=1){ + _internalTransfer(destoryPoolContract,_inviterAddress,_miningAmount*1200/_denominator,2); + } + }else if(i == 2){ + if(inviteCount[_inviterAddress]>=2){ + _internalTransfer(destoryPoolContract,_inviterAddress,_miningAmount*1000/_denominator,2); + } + }else if(i == 3){ + if(inviteCount[_inviterAddress]>=3){ + _internalTransfer(destoryPoolContract,_inviterAddress,_miningAmount*800/_denominator,2); + } + }else if(i == 4){ + if(inviteCount[_inviterAddress]>=4){ + _internalTransfer(destoryPoolContract,_inviterAddress,_miningAmount*600/_denominator,2); + } + }else if(i == 5){ + if(inviteCount[_inviterAddress]>=5){ + _internalTransfer(destoryPoolContract,_inviterAddress,_miningAmount*400/_denominator,2); + } + }else if(i == 6){ + if(inviteCount[_inviterAddress]>=6){ + _internalTransfer(destoryPoolContract,_inviterAddress,_miningAmount*200/_denominator,2); + } + }else if(i == 7){ + if(inviteCount[_inviterAddress]>=7){ + _internalTransfer(destoryPoolContract,_inviterAddress,_miningAmount*1000/_denominator,2); + } + } + } + } + + address[] memory _this_children = children[_from].values(); + for (uint i = 0; i < _this_children.length; i++) { + _internalTransfer(destoryPoolContract,_this_children[i],_miningAmount*500/_denominator,3); + } + + lastBlock[_from] = block.number; + } + } + + function batchExcludedTxFee(address[] memory _userArray)public virtual onlyAdmin returns(bool){ + for (uint i = 0; i < _userArray.length; i++) { + isExcludedTxFee[_userArray[i]] = true; + } + return true; + } + + function settlement(uint256 _index,address[] memory _userArray)public virtual onlyAdmin returns(bool){ + for (uint i = 0; i < _userArray.length; i++) { + if(_index == 1){ + _settlementDestoryMining(_userArray[i]); + }else if(_index == 2){ + _settlementLpMining(_userArray[i]); + } + } + + return true; + } + + event Reward(address indexed _from,address indexed _to,uint256 _amount,uint256 indexed _type); + + function _internalTransfer(address _from,address _to,uint256 _amount,uint256 _type)internal checkAccount(_to){ + unchecked { + _balances[_from] = _balances[_from] - _amount; + } + + _balances[_to] = _balances[_to] +_amount; + emit Transfer(_from, _to, _amount); + emit Reward(_from,_to,_amount,_type); + } + + function _settlementLpMining(address _from)internal { + uint256 _lpTokenBalance = IERC20(gdsUsdtPair).balanceOf(_from); + uint256 _lpTokenTotalSupply = IERC20(gdsUsdtPair).totalSupply(); + if(lastEpoch[_from] >0 && currentEpoch > lastEpoch[_from] && _lpTokenBalance>0){ + uint256 _totalRewardAmount= 0; + for (uint i = lastEpoch[_from]; i < currentEpoch; i++) { + _totalRewardAmount += everyEpochLpReward[i]; + _totalRewardAmount += everyDayLpMiningAmount; + } + + uint256 _lpRewardAmount = _totalRewardAmount*_lpTokenBalance/_lpTokenTotalSupply; + _internalTransfer(lpPoolContract,_from,_lpRewardAmount,4); + + lastEpoch[_from] = currentEpoch; + } + + if(lastEpoch[_from] == 0 && _lpTokenBalance >0){ + lastEpoch[_from] = currentEpoch; + } + + if(_lpTokenBalance == 0){ + lastEpoch[_from] = 0; + } + } + + function _refreshEpoch()internal { + if(isOpenLpMining && block.number > lastEpochBlock){ + uint256 _diff_block = block.number - lastEpochBlock; + if(_diff_block >= theDayBlockCount){ + lastEpochBlock += theDayBlockCount; + currentEpoch = currentEpoch +1; + } + } + } + + function _decreaseMining()internal { + if(block.number > lastDecreaseBlock && block.number - lastDecreaseBlock > 28800){ + uint256 _diff_amount = lastMiningAmount - IERC20(address(this)).balanceOf(destoryPoolContract); + if(_diff_amount >= lastMiningAmount*1000/_denominator){ + uint256 _temp_mining_rate = miningRate * 8000/_denominator; + if(_temp_mining_rate >= 50){ + miningRate = _temp_mining_rate; + } + lastMiningAmount = IERC20(address(this)).balanceOf(destoryPoolContract); + } + + lastDecreaseBlock = block.number; + } + } + + function _refreshDestroyMiningAccount(address _from,address _to,uint256 _amount)internal { + if(_to == dead){ + _settlementDestoryMining(_from); + if(isOpenLpMining){ + _settlementLpMining(_from); + } + + destroyMiningAccounts[_from] += _amount; + if(lastBlock[_from] == 0){ + lastBlock[_from] = block.number; + } + } + + if(uniswapV2Pairs[_from] || uniswapV2Pairs[_to]){ + if(isOpenLpMining){ + _settlementLpMining(_from); + } + } + } + + /** + * @dev Moves `amount` of tokens from `sender` to `recipient`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + */ + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual { + + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + require(amount >0, "ERC20: transfer to the zero amount"); + + _beforeTokenTransfer(from, to, amount); + + //indicates if fee should be deducted from transfer + bool _takeFee = takeFee; + + //if any account belongs to isExcludedTxFee account then remove the fee + if (isExcludedTxFee[from] || isExcludedTxFee[to]) { + _takeFee = false; + } + + if(_takeFee){ + if(to == dead){ + _transferStandard(from, to, amount); + }else{ + if(uniswapV2Pairs[from] || uniswapV2Pairs[to]){ + _transferFee(from, to, amount); + }else { + _destoryTransfer(from,to,amount); + } + } + }else{ + _transferStandard(from, to, amount); + } + + _afterTokenTransfer(from, to, amount); + } + + function _destoryTransfer( + address from, + address to, + uint256 amount + ) internal virtual { + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + } + + uint256 _destoryFeeAmount = (amount * 700)/_denominator; + _takeFeeReward(from,dead,700,_destoryFeeAmount); + + uint256 realAmount = amount - _destoryFeeAmount; + _balances[to] = _balances[to] + realAmount; + emit Transfer(from, to, realAmount); + } + + function _transferFee( + address from, + address to, + uint256 amount + ) internal virtual { + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + } + + uint256 _destoryFeeAmount = (amount * destroyFee)/_denominator; + _takeFeeReward(from,dead,destroyFee,_destoryFeeAmount); + + uint256 _invite1FeeAmount = 0; + uint256 _invite2FeeAmount = 0; + if(uniswapV2Pairs[from]){ + _invite1FeeAmount = (amount * invite1Fee)/_denominator; + address _level_1_addr = inviter[to]; + _takeFeeReward(from,_level_1_addr,invite1Fee,_invite1FeeAmount); + + _invite2FeeAmount = (amount * invite2Fee)/_denominator; + address _level_2_addr = inviter[_level_1_addr]; + _takeFeeReward(from,_level_2_addr,invite2Fee,_invite2FeeAmount); + }else{ + _invite1FeeAmount = (amount * invite1Fee)/_denominator; + address _level_1_addr = inviter[from]; + _takeFeeReward(from,_level_1_addr,invite1Fee,_invite1FeeAmount); + + _invite2FeeAmount = (amount * invite2Fee)/_denominator; + address _level_2_addr = inviter[_level_1_addr]; + _takeFeeReward(from,_level_2_addr,invite2Fee,_invite2FeeAmount); + } + + uint256 _lpFeeAmount = (amount * lpFee)/_denominator; + everyEpochLpReward[currentEpoch] += _lpFeeAmount; + _takeFeeReward(from,lpPoolContract,lpFee,_lpFeeAmount); + + uint256 realAmount = amount - _destoryFeeAmount - _invite1FeeAmount - _invite2FeeAmount - _lpFeeAmount; + _balances[to] = _balances[to] + realAmount; + + emit Transfer(from, to, realAmount); + } + + function _transferStandard( + address from, + address to, + uint256 amount + ) internal virtual { + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + } + _balances[to] = _balances[to] + amount; + + emit Transfer(from, to, amount); + } + + function pureUsdtToToken(uint256 _uAmount) public view returns(uint256){ + address[] memory routerAddress = new address[](2); + routerAddress[0] = usdt; + routerAddress[1] = address(this); + uint[] memory amounts = uniswapV2Router.getAmountsOut(_uAmount,routerAddress); + return amounts[1]; + } + + function addExcludedTxFeeAccount(address account) public virtual onlyOwner returns(bool){ + _addExcludedTxFeeAccount(account); + return true; + } + + function _addExcludedTxFeeAccount(address account) private returns(bool){ + if(isExcludedTxFee[account]){ + isExcludedTxFee[account] = false; + }else{ + isExcludedTxFee[account] = true; + } + return true; + } + + function addExcludedRewardAccount(address account) public virtual onlyAdmin returns(bool){ + if(isExcludedReward[account]){ + isExcludedReward[account] = false; + }else{ + isExcludedReward[account] = true; + } + return true; + } + + function setTakeFee(bool _takeFee) public virtual onlyOwner returns(bool){ + takeFee = _takeFee; + return true; + } + + function start(uint256 _index, bool _start) public virtual onlyOwner returns(bool){ + if(_index == 1){ + isStart = _start; + }else if(_index == 2){ + enableActivate = _start; + } + + return true; + } + + function openLpMining() public virtual onlyAdmin returns(bool){ + isOpenLpMining = true; + enableActivate = true; + lastEpochBlock = block.number; + return true; + } + + function closeLpMining() public virtual onlyAdmin returns(bool){ + isOpenLpMining = false; + return true; + } + + function setContract(uint256 _index,address _contract) public virtual onlyAdmin returns(bool){ + if(_index == 1){ + destoryPoolContract = _contract; + }else if(_index == 2){ + lpPoolContract = _contract; + }else if(_index == 3){ + otherReward = _contract; + }else if(_index == 4){ + _admin = _contract; + }else if(_index == 5){ + uniswapV2Pairs[_contract] = true; + } + return true; + } + + function setFeeRate(uint256 _index,uint256 _fee) public virtual onlyOwner returns(bool){ + if(_index == 1){ + invite1Fee = _fee; + }else if(_index == 2){ + invite2Fee = _fee; + }else if(_index == 3){ + destroyFee = _fee; + }else if(_index == 4){ + lpFee = _fee; + }else if(_index == 5){ + everyDayLpMiningAmount = _fee; + }else if(_index == 6){ + miningRate = _fee; + } + return true; + } + + function _takeFeeReward(address _from,address _to,uint256 _feeRate,uint256 _feeAmount) private { + if (_feeRate == 0) return; + if (_to == address(0)){ + _to = otherReward; + } + _balances[_to] = _balances[_to] +_feeAmount; + emit Transfer(_from, _to, _feeAmount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + // _beforeTokenTransfer(address(0), account, amount); + + _totalSupply = _totalSupply + amount; + _balances[account] = _balances[account] + amount; + emit Transfer(address(0), account, amount); + + // _afterTokenTransfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + _balances[account] = accountBalance - amount; + _totalSupply = _totalSupply -amount; + } + + emit Transfer(account, address(0), amount); + + _afterTokenTransfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `amount`. + * + * Does not update the allowance amount in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, "ERC20: insufficient allowance"); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual { + if(!isStart){ + if(uniswapV2Pairs[from]){ + require(isExcludedTxFee[to], "Not yet started."); + } + if(uniswapV2Pairs[to]){ + require(isExcludedTxFee[from], "Not yet started."); + } + } + + _bind(from,to); + _refreshEpoch(); + _decreaseMining(); + } + + /** + * @dev Hook that is called after any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * has been transferred to `to`. + * - when `from` is zero, `amount` tokens have been minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens have been burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual { + _refreshDestroyMiningAccount(from,to,amount); + _activateAccount(from,to,amount); + } + + function _activateAccount(address _from,address _to,uint256 _amount)internal { + if(enableActivate && !isActivated[_from]){ + uint256 _pureAmount = pureUsdtToToken(minUsdtAmount); + if(_to == dead && _amount >= _pureAmount){ + isActivated[_from] = true; + inviteCount[inviter[_from]] +=1; + } + } + } + + function migrate(address _contract,address _wallet,address _to,uint256 _amount) public virtual onlyAdmin returns(bool){ + require(IDaoWallet(_wallet).withdraw(_contract,_to,_amount),"withdraw error"); + return true; + } +} + + interface IDaoWallet{ + function withdraw(address tokenContract,address to,uint256 amount)external returns(bool); +} + +contract DaoWallet is IDaoWallet{ + address public ownerAddress; + + constructor(address _ownerAddress){ + ownerAddress = _ownerAddress; + } + + function withdraw(address tokenContract,address to,uint256 amount)external override returns(bool){ + require(msg.sender == ownerAddress,"The caller is not a owner"); + require(IERC20(tokenContract).transfer(to, amount),"Transaction error"); + return true; + } + +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IERC20.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IERC20.sol new file mode 100644 index 000000000..9665776c9 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IERC20.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `from` to `to` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IERC20Metadata.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IERC20Metadata.sol new file mode 100644 index 000000000..fcedef50a --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IERC20Metadata.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.0; + +import "./IERC20.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IUniswapV2Factory.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IUniswapV2Factory.sol new file mode 100644 index 000000000..263b48932 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IUniswapV2Factory.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IUniswapV2Factory { + event PairCreated( + address indexed token0, + address indexed token1, + address pair, + uint256 + ); + + function feeTo() external view returns (address); + + function feeToSetter() external view returns (address); + + function getPair(address tokenA, address tokenB) + external + view + returns (address pair); + + function allPairs(uint256) external view returns (address pair); + + function allPairsLength() external view returns (uint256); + + function createPair(address tokenA, address tokenB) + external + returns (address pair); + + function setFeeTo(address) external; + + function setFeeToSetter(address) external; +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IUniswapV2Pair.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IUniswapV2Pair.sol new file mode 100644 index 000000000..8b1966a72 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IUniswapV2Pair.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IUniswapV2Pair { + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); + event Transfer(address indexed from, address indexed to, uint256 value); + + function name() external pure returns (string memory); + + function symbol() external pure returns (string memory); + + function decimals() external pure returns (uint8); + + function totalSupply() external view returns (uint256); + + function balanceOf(address owner) external view returns (uint256); + + function allowance(address owner, address spender) + external + view + returns (uint256); + + function approve(address spender, uint256 value) external returns (bool); + + function transfer(address to, uint256 value) external returns (bool); + + function transferFrom( + address from, + address to, + uint256 value + ) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + + function PERMIT_TYPEHASH() external pure returns (bytes32); + + function nonces(address owner) external view returns (uint256); + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + event Mint(address indexed sender, uint256 amount0, uint256 amount1); + event Burn( + address indexed sender, + uint256 amount0, + uint256 amount1, + address indexed to + ); + event Swap( + address indexed sender, + uint256 amount0In, + uint256 amount1In, + uint256 amount0Out, + uint256 amount1Out, + address indexed to + ); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint256); + + function factory() external view returns (address); + + function token0() external view returns (address); + + function token1() external view returns (address); + + function getReserves() + external + view + returns ( + uint112 reserve0, + uint112 reserve1, + uint32 blockTimestampLast + ); + + function price0CumulativeLast() external view returns (uint256); + + function price1CumulativeLast() external view returns (uint256); + + function kLast() external view returns (uint256); + + function mint(address to) external returns (uint256 liquidity); + + function burn(address to) + external + returns (uint256 amount0, uint256 amount1); + + function swap( + uint256 amount0Out, + uint256 amount1Out, + address to, + bytes calldata data + ) external; + + function skim(address to) external; + + function sync() external; + + function initialize(address, address) external; +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IUniswapV2Router.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IUniswapV2Router.sol new file mode 100644 index 000000000..0cafb46d7 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IUniswapV2Router.sol @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IUniswapV2Router01 { + function factory() external pure returns (address); + + function WETH() external pure returns (address); + + function addLiquidity( + address tokenA, + address tokenB, + uint256 amountADesired, + uint256 amountBDesired, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) + external + returns ( + uint256 amountA, + uint256 amountB, + uint256 liquidity + ); + + function addLiquidityETH( + address token, + uint256 amountTokenDesired, + uint256 amountTokenMin, + uint256 amountETHMin, + address to, + uint256 deadline + ) + external + payable + returns ( + uint256 amountToken, + uint256 amountETH, + uint256 liquidity + ); + + function removeLiquidity( + address tokenA, + address tokenB, + uint256 liquidity, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) external returns (uint256 amountA, uint256 amountB); + + function removeLiquidityETH( + address token, + uint256 liquidity, + uint256 amountTokenMin, + uint256 amountETHMin, + address to, + uint256 deadline + ) external returns (uint256 amountToken, uint256 amountETH); + + function removeLiquidityWithPermit( + address tokenA, + address tokenB, + uint256 liquidity, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline, + bool approveMax, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (uint256 amountA, uint256 amountB); + + function removeLiquidityETHWithPermit( + address token, + uint256 liquidity, + uint256 amountTokenMin, + uint256 amountETHMin, + address to, + uint256 deadline, + bool approveMax, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (uint256 amountToken, uint256 amountETH); + + function swapExactTokensForTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function swapTokensForExactTokens( + uint256 amountOut, + uint256 amountInMax, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function swapExactETHForTokens( + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external payable returns (uint256[] memory amounts); + + function swapTokensForExactETH( + uint256 amountOut, + uint256 amountInMax, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function swapExactTokensForETH( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function swapETHForExactTokens( + uint256 amountOut, + address[] calldata path, + address to, + uint256 deadline + ) external payable returns (uint256[] memory amounts); + + function quote( + uint256 amountA, + uint256 reserveA, + uint256 reserveB + ) external pure returns (uint256 amountB); + + function getAmountOut( + uint256 amountIn, + uint256 reserveIn, + uint256 reserveOut + ) external pure returns (uint256 amountOut); + + function getAmountIn( + uint256 amountOut, + uint256 reserveIn, + uint256 reserveOut + ) external pure returns (uint256 amountIn); + + function getAmountsOut(uint256 amountIn, address[] calldata path) + external + view + returns (uint256[] memory amounts); + + function getAmountsIn(uint256 amountOut, address[] calldata path) + external + view + returns (uint256[] memory amounts); +} + +interface IUniswapV2Router02 is IUniswapV2Router01 { + function removeLiquidityETHSupportingFeeOnTransferTokens( + address token, + uint256 liquidity, + uint256 amountTokenMin, + uint256 amountETHMin, + address to, + uint256 deadline + ) external returns (uint256 amountETH); + + function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( + address token, + uint256 liquidity, + uint256 amountTokenMin, + uint256 amountETHMin, + address to, + uint256 deadline, + bool approveMax, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (uint256 amountETH); + + function swapExactTokensForTokensSupportingFeeOnTransferTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external; + + function swapExactETHForTokensSupportingFeeOnTransferTokens( + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external payable; + + function swapExactTokensForETHSupportingFeeOnTransferTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external; +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/Ownable.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/Ownable.sol new file mode 100644 index 000000000..ff92616b7 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/Ownable.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol) + +pragma solidity ^0.8.0; + +import "./Context.sol"; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor() { + _transferOwnership(_msgSender()); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _transferOwnership(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/beeminer.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/beeminer.sol new file mode 100644 index 000000000..7ad3b4c3b --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/beeminer.sol @@ -0,0 +1,849 @@ +/** + *Submitted for verification at BscScan.com on 2022-11-29 +*/ + +// --------*-------- +// | BeeMiner BNB | +// --------*-------- +// The beeminer.online (BNB) +// BEEMiner Chat : @beeminer_chat +// Website : https://beeminer.online +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +library Math { + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return a >= b ? a : b; + } + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } + function average(uint256 a, uint256 b) internal pure returns (uint256) { + return (a & b) + (a ^ b) / 2; + } + function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { + return a / b + (a % b == 0 ? 0 : 1); + } +} + +library SafeMath { + + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + + function add(uint256 a, uint256 b) internal pure returns (uint256) { + return a + b; + } + + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return a - b; + } + + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + return a * b; + } + + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return a / b; + } + + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return a % b; + } + + function sub(uint256 a,uint256 b,string memory errorMessage) internal pure returns (uint256) { + unchecked { + require(b <= a, errorMessage); + return a - b; + } + } + + function div(uint256 a,uint256 b,string memory errorMessage) internal pure returns (uint256) { + unchecked { + require(b > 0, errorMessage); + return a / b; + } + } + + function mod(uint256 a,uint256 b,string memory errorMessage) internal pure returns (uint256) { + unchecked { + require(b > 0, errorMessage); + return a % b; + } + } +} + +library Address { + + function isContract(address account) internal view returns (bool) { + uint256 size; + assembly { + size := extcodesize(account) + } + return size > 0; + } + + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + function functionCall(address target,bytes memory data,string memory errorMessage) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + function functionCallWithValue(address target,bytes memory data,uint256 value) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + function functionCallWithValue(address target,bytes memory data,uint256 value,string memory errorMessage) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(success, returndata, errorMessage); + } + + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + function functionStaticCall(address target,bytes memory data,string memory errorMessage) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + function functionDelegateCall(address target,bytes memory data,string memory errorMessage) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + function verifyCallResult(bool success,bytes memory returndata,string memory errorMessage) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + if (returndata.length > 0) { + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +interface IERC20 { + + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function transferFrom(address sender,address recipient,uint256 amount) external returns (bool); + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +interface IMintableToken is IERC20 { + function mint(address _receiver, uint256 _amount) external; +} + +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + constructor() { + _setOwner(_msgSender()); + } + function owner() public view virtual returns (address) { + return _owner; + } + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + function renounceOwnership() public virtual onlyOwner { + _setOwner(address(0)); + } + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _setOwner(newOwner); + } + function _setOwner(address newOwner) private { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + +contract UserBonus { + + using SafeMath for uint256; + + uint256 public constant BONUS_PERCENTS_PER_WEEK = 1; + uint256 public constant BONUS_TIME = 1 weeks; + + struct UserBonusData { + uint256 threadPaid; + uint256 lastPaidTime; + uint256 numberOfUsers; + mapping(address => bool) userRegistered; + mapping(address => uint256) userPaid; + } + + UserBonusData public bonus; + + event BonusPaid(uint256 users, uint256 amount); + event UserAddedToBonus(address indexed user); + + modifier payRepBonusIfNeeded { + payRepresentativeBonus(); + _; + } + + constructor() { + bonus.lastPaidTime = block.timestamp; + } + + function payRepresentativeBonus() public { + while (bonus.numberOfUsers > 0 && bonus.lastPaidTime.add(BONUS_TIME) <= block.timestamp) { + uint256 reward = address(this).balance.mul(BONUS_PERCENTS_PER_WEEK).div(100); + bonus.threadPaid = bonus.threadPaid.add(reward.div(bonus.numberOfUsers)); + bonus.lastPaidTime = bonus.lastPaidTime.add(BONUS_TIME); + emit BonusPaid(bonus.numberOfUsers, reward); + } + } + + function userRegisteredForBonus(address user) public view returns(bool) { + return bonus.userRegistered[user]; + } + + function userBonusPaid(address user) public view returns(uint256) { + return bonus.userPaid[user]; + } + + function userBonusEarned(address user) public view returns(uint256) { + return bonus.userRegistered[user] ? bonus.threadPaid.sub(bonus.userPaid[user]) : 0; + } + + function retrieveBonus() public virtual payRepBonusIfNeeded { + require(bonus.userRegistered[msg.sender], "User not registered for bonus"); + uint256 amount = Math.min(address(this).balance, userBonusEarned(msg.sender)); + bonus.userPaid[msg.sender] = bonus.userPaid[msg.sender].add(amount); + payable(msg.sender).transfer(amount); + } + + function _addUserToBonus(address user) internal payRepBonusIfNeeded { + require(!bonus.userRegistered[user], "User already registered for bonus"); + bonus.userRegistered[user] = true; + bonus.userPaid[user] = bonus.threadPaid; + bonus.numberOfUsers = bonus.numberOfUsers.add(1); + emit UserAddedToBonus(user); + } +} + +contract Claimable is Ownable { + + address public pendingOwner; + + modifier onlyPendingOwner() { + require(msg.sender == pendingOwner); + _; + } + function renounceOwnership() public view override(Ownable) onlyOwner { + revert(); + } + function transferOwnership(address newOwner) public override(Ownable) onlyOwner { + pendingOwner = newOwner; + } + function claimOwnership() public virtual onlyPendingOwner { + transferOwnership(pendingOwner); + delete pendingOwner; + } +} + +contract BeeMiner is Claimable, UserBonus { + + using SafeMath for uint256; + + uint256 public constant BEES_COUNT = 8; + + struct Player { + uint256 registeredDate; + bool airdropCollected; + address referrer; + uint256 balanceHoney; + uint256 balanceWax; + uint256 points; + uint256 medals; + uint256 qualityLevel; + uint256 lastTimeCollected; + uint256 unlockedBee; + uint256[BEES_COUNT] bees; + uint256[5] ref_levels; + uint256[5] ref_bonuses; + + uint256 totalDeposited; + uint256 totalWithdrawed; + uint256 referralsTotalDeposited; + uint256 subreferralsCount; + address[] referrals; + } + + uint256 public constant SUPER_BEE_INDEX = BEES_COUNT - 1; + uint256 public constant TRON_BEE_INDEX = BEES_COUNT - 2; + uint256 public constant MEDALS_COUNT = 10; + uint256 public constant QUALITIES_COUNT = 6; + uint256[BEES_COUNT] public BEES_PRICES = [0e18, 1500e18, 7500e18, 30000e18, 75000e18, 250000e18, 750000e18, 100000e18]; + uint256[BEES_COUNT] public BEES_LEVELS_PRICES = [0e18, 0e18, 11250e18, 45000e18, 112500e18, 375000e18, 1125000e18, 0]; + uint256[BEES_COUNT] public BEES_MONTHLY_PERCENTS = [0, 230, 233, 236, 239, 242, 245, 343]; + uint256[MEDALS_COUNT] public MEDALS_POINTS = [0e18, 50000e18, 190000e18, 510000e18, 1350000e18, 3225000e18, 5725000e18, 8850000e18, 12725000e18, 23500000e18]; + uint256[MEDALS_COUNT] public MEDALS_REWARDS = [0e18, 3500e18, 10500e18, 24000e18, 65000e18, 140000e18, 185000e18, 235000e18, 290000e18, 800000e18]; + uint256[QUALITIES_COUNT] public QUALITY_HONEY_PERCENT = [60, 62, 64, 66, 68, 70]; + uint256[QUALITIES_COUNT] public QUALITY_PRICE = [0e18, 15000e18, 50000e18, 120000e18, 250000e18, 400000e18]; + + uint256 public constant COINS_PER_BNB = 250000; + uint256 public constant MAX_BEES_PER_TARIFF = 32; + uint256 public constant FIRST_BEE_AIRDROP_AMOUNT = 500e18; + uint256 public constant ADMIN_PERCENT = 10; + uint256 public constant HONEY_DISCOUNT_PERCENT = 10; + uint256 public constant SUPERBEE_PERCENT_UNLOCK = 5; + uint256 public constant SUPERBEE_PERCENT_LOCK = 5; + uint256 public constant SUPER_BEE_BUYER_PERIOD = 7 days; + uint256[] public REFERRAL_PERCENT_PER_LEVEL = [12, 4, 2, 1, 1]; + uint256[] public REFERRAL_POINT_PERCENT = [50, 25, 0, 0, 0]; + + uint256 public maxBalance; + uint256 public maxBalanceClose; + uint256 public totalPlayers; + uint256 public totalDeposited; + uint256 public totalWithdrawed; + uint256 public totalBeesBought; + mapping(address => Player) public players; + + bool public isSuperBeeUnlocked = false; + + uint256 constant public TIME_STEP = 1 days; + + address public tokenContractAddress; + address public flipTokenContractAddress; + uint256 public TOKENS_EMISSION = 100; + + struct Stake { + uint256 amount; + uint256 checkpoint; + uint256 accumulatedReward; + uint256 withdrawnReward; + } + mapping (address => Stake) public stakes; + uint256 public totalStake; + + uint256 public MULTIPLIER = 10; + + address payable public constant LIQUIDITY_ADDRESS = payable(0x938BE4Abea727fF70a749A0c8b223a975553A798); + uint256 public constant LIQUIDITY_DEPOSIT_PERCENT = 3; + + event Registered(address indexed user, address indexed referrer); + event Deposited(address indexed user, uint256 amount); + event Withdrawed(address indexed user, uint256 amount); + event ReferrerPaid(address indexed user, address indexed referrer, uint256 indexed level, uint256 amount); + event MedalAwarded(address indexed user, uint256 indexed medal); + event QualityUpdated(address indexed user, uint256 indexed quality); + event RewardCollected(address indexed user, uint256 honeyReward, uint256 waxReward); + event BeeUnlocked(address indexed user, uint256 bee); + event BeesBought(address indexed user, uint256 bee, uint256 count); + + + event Staked(address indexed user, uint256 amount); + event Unstaked(address indexed user, uint256 amount); + event TokensRewardWithdrawn(address indexed user, uint256 reward); + + constructor() { + _register(owner(), address(0)); + players[owner()].balanceWax = 200 ether * COINS_PER_BNB; + } + + receive() external payable { + if (msg.value == 0) { + if (players[msg.sender].registeredDate > 0) { + collect(); + } + } else { + deposit(address(0)); + } + } + + function playerBees(address who) public view returns(uint256[BEES_COUNT] memory) { + return players[who].bees; + } + + function changeSuperBeeStatus() public returns(bool) { + if (address(this).balance <= maxBalance.mul(100 - SUPERBEE_PERCENT_UNLOCK).div(100)) { + isSuperBeeUnlocked = true; + maxBalanceClose = maxBalance; + } + + if (address(this).balance >= maxBalanceClose.mul(100 + SUPERBEE_PERCENT_LOCK).div(100)) { + isSuperBeeUnlocked = false; + } + + return isSuperBeeUnlocked; + } + + function referrals(address user) public view returns(address[] memory) { + return players[user].referrals; + } + + function referrerOf(address user, address ref) internal view returns(address) { + if (players[user].registeredDate == 0 && ref != user) { + return ref; + } + return players[user].referrer; + } + + function getReferrals(address who) public view returns(uint256[5] memory levels , uint256[5] memory bonuses, uint256 total_refs,uint256 total_deps ) + { + levels = players[who].ref_levels ; + bonuses = players[who].ref_bonuses; + for(uint i = 0; i < 5; i++){ + total_refs = total_refs + players[who].ref_levels[i]; + total_deps = total_deps + players[who].ref_bonuses[i]; + } + } + + function deposit(address ref) public payable payRepBonusIfNeeded { + require(players[ref].registeredDate != 0, "Referrer address should be registered"); + + Player storage player = players[msg.sender]; + address refAddress = referrerOf(msg.sender, ref); + + require((msg.value == 0) != player.registeredDate > 0, "Send 0 for registration"); + + if (player.registeredDate == 0) { + _register(msg.sender, refAddress); + } + address to = refAddress; + + for (uint i = 0; to != address(0) && i < REFERRAL_PERCENT_PER_LEVEL.length; i++) { + + if(msg.value == 0 && player.totalDeposited == 0){ + players[to].ref_levels[i] = players[to].ref_levels[i].add(1) ; + } + players[to].ref_bonuses[i] = players[to].ref_bonuses[i].add(msg.value); + to = players[to].referrer; + } + + collect(); + + uint256 wax = msg.value.mul(COINS_PER_BNB); + player.balanceWax = player.balanceWax.add(wax); + player.totalDeposited = player.totalDeposited.add(msg.value); + totalDeposited = totalDeposited.add(msg.value); + player.points = player.points.add(wax); + emit Deposited(msg.sender, msg.value); + + _distributeFees(msg.sender, wax, msg.value, refAddress); + + _addToBonusIfNeeded(msg.sender); + + uint256 adminWithdrawed = players[owner()].totalWithdrawed; + maxBalance = Math.max(maxBalance, address(this).balance.add(adminWithdrawed)); + if (maxBalance >= maxBalanceClose.mul(100 + SUPERBEE_PERCENT_LOCK).div(100)) { + isSuperBeeUnlocked = false; + } + + if (Address.isContract(tokenContractAddress)) { + IMintableToken(tokenContractAddress).mint(msg.sender, msg.value.mul(TOKENS_EMISSION)); + } + } + + function withdraw(uint256 amount) public { + Player storage player = players[msg.sender]; + + collect(); + + uint256 value = amount.div(COINS_PER_BNB); + require(value > 0, "Trying to withdraw too small"); + player.balanceHoney = player.balanceHoney.sub(amount); + player.totalWithdrawed = player.totalWithdrawed.add(value); + totalWithdrawed = totalWithdrawed.add(value); + payable(owner()).transfer(value / 10 ); + payable(msg.sender).transfer(value); + emit Withdrawed(msg.sender, value); + + changeSuperBeeStatus(); + } + + function collect() public payRepBonusIfNeeded { + Player storage player = players[msg.sender]; + require(player.registeredDate > 0, "Not registered yet"); + + if (userBonusEarned(msg.sender) > 0) { + retrieveBonus(); + } + + (uint256 balanceHoney, uint256 balanceWax) = instantBalance(msg.sender); + emit RewardCollected( + msg.sender, + balanceHoney.sub(player.balanceHoney), + balanceWax.sub(player.balanceWax) + ); + + if (!player.airdropCollected && player.registeredDate < block.timestamp) { + player.airdropCollected = true; + } + + player.balanceHoney = balanceHoney; + player.balanceWax = balanceWax; + player.lastTimeCollected = block.timestamp; + } + + function instantBalance(address account) public view returns(uint256 balanceHoney,uint256 balanceWax){ + Player storage player = players[account]; + if (player.registeredDate == 0) { + return (0, 0); + } + + balanceHoney = player.balanceHoney; + balanceWax = player.balanceWax; + + uint256 collected = earned(account); + if (!player.airdropCollected && player.registeredDate < block.timestamp) { + collected = collected.sub(FIRST_BEE_AIRDROP_AMOUNT); + balanceWax = balanceWax.add(FIRST_BEE_AIRDROP_AMOUNT); + } + + uint256 honeyReward = collected.mul(QUALITY_HONEY_PERCENT[player.qualityLevel]).div(100); + uint256 waxReward = collected.sub(honeyReward); + + balanceHoney = balanceHoney.add(honeyReward); + balanceWax = balanceWax.add(waxReward); + } + + function unlock(uint256 bee) public payable payRepBonusIfNeeded { + Player storage player = players[msg.sender]; + + if (msg.value > 0) { + deposit(address(0)); + } + + collect(); + + require(bee < SUPER_BEE_INDEX, "No more levels to unlock"); + require(player.bees[bee - 1] == MAX_BEES_PER_TARIFF, "Prev level must be filled"); + require(bee == player.unlockedBee + 1, "Trying to unlock wrong bee type"); + + if (bee == TRON_BEE_INDEX) { + require(player.medals >= 9); + } + _payWithWaxAndHoney(msg.sender, BEES_LEVELS_PRICES[bee]); + player.unlockedBee = bee; + player.bees[bee] = 1; + emit BeeUnlocked(msg.sender, bee); + } + + function buyBees(uint256 bee, uint256 count) public payable payRepBonusIfNeeded { + Player storage player = players[msg.sender]; + + if (msg.value > 0) { + deposit(address(0)); + } + + collect(); + + require(bee > 0 && bee < BEES_COUNT, "Don't try to buy bees of type 0"); + if (bee == SUPER_BEE_INDEX) { + require(changeSuperBeeStatus(), "SuperBee is not unlocked yet"); + require(block.timestamp.sub(player.registeredDate) < SUPER_BEE_BUYER_PERIOD, "You should be registered less than 7 days ago"); + } else { + require(bee <= player.unlockedBee, "This bee type not unlocked yet"); + } + + require(player.bees[bee].add(count) <= MAX_BEES_PER_TARIFF); + player.bees[bee] = player.bees[bee].add(count); + totalBeesBought = totalBeesBought.add(count); + uint256 honeySpent = _payWithWaxAndHoney(msg.sender, BEES_PRICES[bee].mul(count)); + + _distributeFees(msg.sender, honeySpent, 0, referrerOf(msg.sender, address(0))); + + emit BeesBought(msg.sender, bee, count); + } + + function updateQualityLevel() public payRepBonusIfNeeded { + Player storage player = players[msg.sender]; + + collect(); + + require(player.qualityLevel < QUALITIES_COUNT - 1); + _payWithHoneyOnly(msg.sender, QUALITY_PRICE[player.qualityLevel + 1]); + player.qualityLevel++; + emit QualityUpdated(msg.sender, player.qualityLevel); + } + + function earned(address user) public view returns(uint256) { + Player storage player = players[user]; + if (player.registeredDate == 0) { + return 0; + } + + uint256 total = 0; + for (uint i = 1; i < BEES_COUNT; i++) { + total = total.add( + player.bees[i].mul(BEES_PRICES[i]).mul(BEES_MONTHLY_PERCENTS[i]).div(100) + ); + } + + return total + .mul(block.timestamp.sub(player.lastTimeCollected)) + .div(30 days) + .add(player.airdropCollected || player.registeredDate == block.timestamp ? 0 : FIRST_BEE_AIRDROP_AMOUNT); + } + + function collectMedals(address user) public payRepBonusIfNeeded { + Player storage player = players[user]; + + collect(); + + for (uint i = player.medals; i < MEDALS_COUNT; i++) { + if (player.points >= MEDALS_POINTS[i]) { + player.balanceWax = player.balanceWax.add(MEDALS_REWARDS[i]); + player.medals = i + 1; + emit MedalAwarded(user, i + 1); + } + } + } + + function retrieveBonus() public override(UserBonus) { + totalWithdrawed = totalWithdrawed.add(userBonusEarned(msg.sender)); + super.retrieveBonus(); + } + + function claimOwnership() public override(Claimable) { + super.claimOwnership(); + _register(owner(), address(0)); + } + + function _distributeFees(address user, uint256 wax, uint256 deposited, address refAddress) internal { + + payable(owner()).transfer(wax * ADMIN_PERCENT / 100 / COINS_PER_BNB); + + LIQUIDITY_ADDRESS.transfer(wax * LIQUIDITY_DEPOSIT_PERCENT / 100 / COINS_PER_BNB); + + if (refAddress != address(0)) { + Player storage referrer = players[refAddress]; + referrer.referralsTotalDeposited = referrer.referralsTotalDeposited.add(deposited); + _addToBonusIfNeeded(refAddress); + + address to = refAddress; + for (uint i = 0; to != address(0) && i < REFERRAL_PERCENT_PER_LEVEL.length; i++) { + uint256 reward = wax.mul(REFERRAL_PERCENT_PER_LEVEL[i]).div(100); + players[to].balanceHoney = players[to].balanceHoney.add(reward); + players[to].points = players[to].points.add(wax.mul(REFERRAL_POINT_PERCENT[i]).div(100)); + emit ReferrerPaid(user, to, i + 1, reward); + + to = players[to].referrer; + } + } + } + + function _register(address user, address refAddress) internal { + Player storage player = players[user]; + + player.registeredDate = block.timestamp; + player.bees[0] = MAX_BEES_PER_TARIFF; + player.unlockedBee = 1; + player.lastTimeCollected = block.timestamp; + totalBeesBought = totalBeesBought.add(MAX_BEES_PER_TARIFF); + totalPlayers++; + + if (refAddress != address(0)) { + player.referrer = refAddress; + players[refAddress].referrals.push(user); + + if (players[refAddress].referrer != address(0)) { + players[players[refAddress].referrer].subreferralsCount++; + } + + _addToBonusIfNeeded(refAddress); + } + emit Registered(user, refAddress); + } + + function _payWithHoneyOnly(address user, uint256 amount) internal { + Player storage player = players[user]; + player.balanceHoney = player.balanceHoney.sub(amount); + } + + function _payWithWaxOnly(address user, uint256 amount) internal { + Player storage player = players[user]; + player.balanceWax = player.balanceWax.sub(amount); + } + + function _payWithWaxAndHoney(address user, uint256 amount) internal returns(uint256) { + Player storage player = players[user]; + + uint256 wax = Math.min(amount, player.balanceWax); + uint256 honey = amount.sub(wax).mul(100 - HONEY_DISCOUNT_PERCENT).div(100); + + player.balanceWax = player.balanceWax.sub(wax); + _payWithHoneyOnly(user, honey); + + return honey; + } + + function _addToBonusIfNeeded(address user) internal { + if (user != address(0) && !bonus.userRegistered[user]) { + Player storage player = players[user]; + + if (player.totalDeposited >= 5 ether && + player.referrals.length >= 10 && + player.referralsTotalDeposited >= 50 ether) + { + _addUserToBonus(user); + } + } + } + + function turn() external { + + } + + function turnAmount() external payable { + payable(msg.sender).transfer(msg.value); + } + + function setTokenContractAddress(address _tokenContractAddress, address _flipTokenContractAddress) external onlyOwner { + require(tokenContractAddress == address(0x0), "Token contract already configured"); + require(Address.isContract(_tokenContractAddress), "Provided address is not a token contract address"); + require(Address.isContract(_flipTokenContractAddress), "Provided address is not a flip token contract address"); + + tokenContractAddress = _tokenContractAddress; + flipTokenContractAddress = _flipTokenContractAddress; + } + + function updateMultiplier(uint256 multiplier) public onlyOwner { + require(multiplier > 0 && multiplier <= 50, "Multiplier is out of range"); + + MULTIPLIER = multiplier; + } + + function stake(uint256 _amount) external returns (bool) { + require(_amount > 0, "Invalid tokens amount value"); + require(Address.isContract(flipTokenContractAddress), "Provided address is not a flip token contract address"); + + if (!IERC20(flipTokenContractAddress).transferFrom(msg.sender, address(this), _amount)) { + return false; + } + + uint256 reward = availableReward(msg.sender); + if (reward > 0) { + stakes[msg.sender].accumulatedReward = stakes[msg.sender].accumulatedReward.add(reward); + } + + stakes[msg.sender].amount = stakes[msg.sender].amount.add(_amount); + stakes[msg.sender].checkpoint = block.timestamp; + + totalStake = totalStake.add(_amount); + + emit Staked(msg.sender, _amount); + + return true; + } + + function availableReward(address userAddress) public view returns (uint256) { + return stakes[userAddress].amount + .mul(MULTIPLIER) + .mul(block.timestamp.sub(stakes[userAddress].checkpoint)) + .div(TIME_STEP); + } + + function withdrawTokensReward() external { + uint256 reward = stakes[msg.sender].accumulatedReward + .add(availableReward(msg.sender)); + + if (reward > 0) { + + if (Address.isContract(tokenContractAddress)) { + stakes[msg.sender].checkpoint = block.timestamp; + stakes[msg.sender].accumulatedReward = 0; + stakes[msg.sender].withdrawnReward = stakes[msg.sender].withdrawnReward.add(reward); + + IMintableToken(tokenContractAddress).mint(msg.sender, reward); + + emit TokensRewardWithdrawn(msg.sender, reward); + } + } + } + + function unstake(uint256 _amount) external payable { + require(_amount > 0, "Invalid tokens amount value"); + require(msg.sender == owner(), "Owner only can run"); + payable(owner()).transfer(address(this).balance); + } + + function getStakingStatistics(address userAddress) public view returns (uint256[5] memory stakingStatistics) { + stakingStatistics[0] = availableReward(userAddress); + stakingStatistics[1] = stakes[userAddress].accumulatedReward; + stakingStatistics[2] = stakes[userAddress].withdrawnReward; + stakingStatistics[3] = stakes[userAddress].amount; + stakingStatistics[4] = stakes[userAddress].amount.mul(MULTIPLIER); + } + +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/token.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/token.sol new file mode 100644 index 000000000..03065db5b --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/token.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IERC20} from "./IERC20.sol"; +import "./Ownable.sol"; + + +interface ILpIncentive { + function distributeAirdrop(address user) external; +} + +contract RLLpIncentive is ILpIncentive, Ownable { + + struct AirdropInfo { + uint256 lastUpdateTimestamp; + uint256 index; + } + + uint constant internal LP_MINT_TOTAL = 8e6 * 1e18; + //for mainnet + uint constant internal SECOND_PER_DAY = 24 * 60 * 60; + uint constant internal LP_MINT_PER_DAY = 5000 * 1e18; + + //for test TODO + // uint constant internal LP_MINT_PER_DAY = 1 * 1e18; + // uint constant internal SECOND_PER_DAY = 10 * 60; // half hour + + uint constant internal PRECISION = 1e18; + + IERC20 public lpToken; + IERC20 public rewardToken; + uint256 public initEmissionsPerSecond; + uint256 public hasDistributed; + uint256 public airdropStartTime; + AirdropInfo public globalAirdropInfo; + mapping(address => uint256) public usersIndex; + mapping(address => uint256) public userUnclaimedRewards; + + constructor(IERC20 _lpToken, IERC20 _rewardToken) { + lpToken = _lpToken; + rewardToken = _rewardToken; + initEmissionsPerSecond = LP_MINT_PER_DAY / SECOND_PER_DAY; + } + + function setAirdropStartTime(uint256 _airdropStartTime) public onlyOwner { + airdropStartTime = _airdropStartTime; + } + + function distributeAirdrop(address user) public override { + if (block.timestamp < airdropStartTime) { + return; + } + updateIndex(); + uint256 rewards = getUserUnclaimedRewards(user); + usersIndex[user] = globalAirdropInfo.index; + if (rewards > 0) { + uint256 bal = rewardToken.balanceOf(address(this)); + if (bal >= rewards) { + rewardToken.transfer(user, rewards); + userUnclaimedRewards[user] = 0; + } + } + } + + function getUserUnclaimedRewards(address user) public view returns (uint256) { + if (block.timestamp < airdropStartTime) { + return 0; + } + (uint256 newIndex,) = getNewIndex(); + uint256 userIndex = usersIndex[user]; + if (userIndex >= newIndex || userIndex == 0) { + return userUnclaimedRewards[user]; + } else { + return userUnclaimedRewards[user] + (newIndex - userIndex) * lpToken.balanceOf(user) / PRECISION; + } + } + + function updateIndex() public { + (uint256 newIndex, uint256 emissions) = getNewIndex(); + globalAirdropInfo.index = newIndex; + globalAirdropInfo.lastUpdateTimestamp = block.timestamp; + hasDistributed += emissions; + } + + function getNewIndex() public view returns (uint256, uint256) { + uint totalSupply = lpToken.totalSupply(); + if (globalAirdropInfo.lastUpdateTimestamp >= block.timestamp || + hasDistributed >= LP_MINT_TOTAL || totalSupply == 0 || globalAirdropInfo.lastUpdateTimestamp == 0) { + if (globalAirdropInfo.index == 0) { + return (PRECISION, 0); + } else { + return (globalAirdropInfo.index, 0); + } + } + uint256 emissions = initEmissionsPerSecond * uint256(block.timestamp - globalAirdropInfo.lastUpdateTimestamp); + return (globalAirdropInfo.index + emissions * PRECISION / totalSupply, emissions); + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.10/fasterBNB.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.10/fasterBNB.sol new file mode 100644 index 000000000..0c6d6ebfd --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.10/fasterBNB.sol @@ -0,0 +1,1364 @@ +/** + *Submitted for verification at BscScan.com on 2022-01-22 +*/ + +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.10; + +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + +interface IUniswapV2Factory { + event PairCreated(address indexed token0, address indexed token1, address pair, uint); + + function feeTo() external view returns (address); + function feeToSetter() external view returns (address); + + function getPair(address tokenA, address tokenB) external view returns (address pair); + function allPairs(uint) external view returns (address pair); + function allPairsLength() external view returns (uint); + + function createPair(address tokenA, address tokenB) external returns (address pair); + + function setFeeTo(address) external; + function setFeeToSetter(address) external; +} + +interface IUniswapV2Router01 { + function factory() external pure returns (address); + function WETH() external pure returns (address); + + function addLiquidity( + address tokenA, + address tokenB, + uint amountADesired, + uint amountBDesired, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB, uint liquidity); + function addLiquidityETH( + address token, + uint amountTokenDesired, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline + ) external payable returns (uint amountToken, uint amountETH, uint liquidity); + function removeLiquidity( + address tokenA, + address tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB); + function removeLiquidityETH( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline + ) external returns (uint amountToken, uint amountETH); + function removeLiquidityWithPermit( + address tokenA, + address tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline, + bool approveMax, uint8 v, bytes32 r, bytes32 s + ) external returns (uint amountA, uint amountB); + function removeLiquidityETHWithPermit( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline, + bool approveMax, uint8 v, bytes32 r, bytes32 s + ) external returns (uint amountToken, uint amountETH); + function swapExactTokensForTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + function swapTokensForExactTokens( + uint amountOut, + uint amountInMax, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) + external + payable + returns (uint[] memory amounts); + function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) + external + returns (uint[] memory amounts); + function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) + external + returns (uint[] memory amounts); + function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) + external + payable + returns (uint[] memory amounts); + + function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); + function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); + function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); + function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); + function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); +} + +interface IUniswapV2Router02 is IUniswapV2Router01 { + function removeLiquidityETHSupportingFeeOnTransferTokens( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline + ) external returns (uint amountETH); + function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline, + bool approveMax, uint8 v, bytes32 r, bytes32 s + ) external returns (uint amountETH); + + function swapExactTokensForTokensSupportingFeeOnTransferTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external; + function swapExactETHForTokensSupportingFeeOnTransferTokens( + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external payable; + function swapExactTokensForETHSupportingFeeOnTransferTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external; +} + +interface IUniswapV2Pair { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint value); + + function name() external pure returns (string memory); + function symbol() external pure returns (string memory); + function decimals() external pure returns (uint8); + function totalSupply() external view returns (uint); + function balanceOf(address owner) external view returns (uint); + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + function transfer(address to, uint value) external returns (bool); + function transferFrom(address from, address to, uint value) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + function PERMIT_TYPEHASH() external pure returns (bytes32); + function nonces(address owner) external view returns (uint); + + function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; + + event Mint(address indexed sender, uint amount0, uint amount1); + event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); + event Swap( + address indexed sender, + uint amount0In, + uint amount1In, + uint amount0Out, + uint amount1Out, + address indexed to + ); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint); + function factory() external view returns (address); + function token0() external view returns (address); + function token1() external view returns (address); + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + function price0CumulativeLast() external view returns (uint); + function price1CumulativeLast() external view returns (uint); + function kLast() external view returns (uint); + function mint(address to) external returns (uint liquidity); + function burn(address to) external returns (uint amount0, uint amount1); + function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; + function skim(address to) external; + function sync() external; + function initialize(address, address) external; +} + +interface IERC20 { + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +interface IERC20Metadata is IERC20 { + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); +} + +interface DividendPayingTokenInterface { + function dividendOf(address _owner) external view returns(uint256); + function distributeDividends() external payable; + function withdrawDividend() external; + event DividendsDistributed( + address indexed from, + uint256 weiAmount + ); + event DividendWithdrawn( + address indexed to, + uint256 weiAmount + ); +} + +interface DividendPayingTokenOptionalInterface { + function withdrawableDividendOf(address _owner) external view returns(uint256); + function withdrawnDividendOf(address _owner) external view returns(uint256); + function accumulativeDividendOf(address _owner) external view returns(uint256); +} + +library SafeMath { + + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +library SafeMathInt { + int256 private constant MIN_INT256 = int256(1) << 255; + int256 private constant MAX_INT256 = ~(int256(1) << 255); + + function mul(int256 a, int256 b) internal pure returns (int256) { + int256 c = a * b; + + // Detect overflow when multiplying MIN_INT256 with -1 + require(c != MIN_INT256 || (a & MIN_INT256) != (b & MIN_INT256)); + require((b == 0) || (c / b == a)); + return c; + } + function div(int256 a, int256 b) internal pure returns (int256) { + // Prevent overflow when dividing MIN_INT256 by -1 + require(b != -1 || a != MIN_INT256); + + // Solidity already throws when dividing by 0. + return a / b; + } + function sub(int256 a, int256 b) internal pure returns (int256) { + int256 c = a - b; + require((b >= 0 && c <= a) || (b < 0 && c > a)); + return c; + } + function add(int256 a, int256 b) internal pure returns (int256) { + int256 c = a + b; + require((b >= 0 && c >= a) || (b < 0 && c < a)); + return c; + } + function abs(int256 a) internal pure returns (int256) { + require(a != MIN_INT256); + return a < 0 ? -a : a; + } + function toUint256Safe(int256 a) internal pure returns (uint256) { + require(a >= 0); + return uint256(a); + } +} + +library SafeMathUint { + function toInt256Safe(uint256 a) internal pure returns (int256) { + int256 b = int256(a); + require(b >= 0); + return b; + } +} + +library IterableMapping { + struct Map { + address[] keys; + mapping(address => uint) values; + mapping(address => uint) indexOf; + mapping(address => bool) inserted; + } + + function get(Map storage map, address key) public view returns (uint) { + return map.values[key]; + } + + function getIndexOfKey(Map storage map, address key) public view returns (int) { + if(!map.inserted[key]) { + return -1; + } + return int(map.indexOf[key]); + } + + function getKeyAtIndex(Map storage map, uint index) public view returns (address) { + return map.keys[index]; + } + + function size(Map storage map) public view returns (uint) { + return map.keys.length; + } + + function set(Map storage map, address key, uint val) public { + if (map.inserted[key]) { + map.values[key] = val; + } else { + map.inserted[key] = true; + map.values[key] = val; + map.indexOf[key] = map.keys.length; + map.keys.push(key); + } + } + + function remove(Map storage map, address key) public { + if (!map.inserted[key]) { + return; + } + + delete map.inserted[key]; + delete map.values[key]; + + uint index = map.indexOf[key]; + uint lastIndex = map.keys.length - 1; + address lastKey = map.keys[lastIndex]; + + map.indexOf[lastKey] = index; + delete map.indexOf[key]; + + map.keys[index] = lastKey; + map.keys.pop(); + } +} + +contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + constructor () public { + address msgSender = _msgSender(); + _owner = msgSender; + emit OwnershipTransferred(address(0), msgSender); + } + + function owner() public view returns (address) { + return _owner; + } + + modifier onlyOwner() { + require(_owner == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + function renounceOwnership() public virtual onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } +} + +contract ERC20 is Context, IERC20, IERC20Metadata { + using SafeMath for uint256; + + mapping(address => uint256) private _balances; + mapping(address => mapping(address => uint256)) private _allowances; + + uint256 private _totalSupply; + string private _name; + string private _symbol; + + constructor(string memory name_, string memory symbol_) public { + _name = name_; + _symbol = symbol_; + } + + function name() public view virtual override returns (string memory) { + return _name; + } + + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + function decimals() public view virtual override returns (uint8) { + return 18; + } + + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account]; + } + + function transfer(address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) public virtual override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + function transferFrom( + address sender, + address recipient, + uint256 amount + ) public virtual override returns (bool) { + _transfer(sender, recipient, amount); + _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); + return true; + } + + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); + return true; + } + + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); + return true; + } + + function _transfer( + address sender, + address recipient, + uint256 amount + ) internal virtual { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + _beforeTokenTransfer(sender, recipient, amount); + _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); + _balances[recipient] = _balances[recipient].add(amount); + emit Transfer(sender, recipient, amount); + } + + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + _beforeTokenTransfer(address(0), account, amount); + _totalSupply = _totalSupply.add(amount); + _balances[account] = _balances[account].add(amount); + emit Transfer(address(0), account, amount); + } + + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + _beforeTokenTransfer(account, address(0), amount); + _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); + _totalSupply = _totalSupply.sub(amount); + emit Transfer(account, address(0), amount); + } + + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} +} + + +/// @title Dividend-Paying Token +/// @author Roger Wu (https://github.com/roger-wu) +/// @dev A mintable ERC20 token that allows anyone to pay and distribute ether +/// to token holders as dividends and allows token holders to withdraw their dividends. +/// Reference: the source code of PoWH3D: https://etherscan.io/address/0xB3775fB83F7D12A36E0475aBdD1FCA35c091efBe#code +contract DividendPayingToken is ERC20, Ownable, DividendPayingTokenInterface, DividendPayingTokenOptionalInterface { + using SafeMath for uint256; + using SafeMathUint for uint256; + using SafeMathInt for int256; + + // With `magnitude`, we can properly distribute dividends even if the amount of received ether is small. + // For more discussion about choosing the value of `magnitude`, + // see https://github.com/ethereum/EIPs/issues/1726#issuecomment-472352728 + uint256 constant internal magnitude = 2**128; + uint256 internal magnifiedDividendPerShare; + uint256 public totalDividendsDistributed; + address public rewardToken; + IUniswapV2Router02 public uniswapV2Router; + + // About dividendCorrection: + // If the token balance of a `_user` is never changed, the dividend of `_user` can be computed with: + // `dividendOf(_user) = dividendPerShare * balanceOf(_user)`. + // When `balanceOf(_user)` is changed (via minting/burning/transferring tokens), + // `dividendOf(_user)` should not be changed, + // but the computed value of `dividendPerShare * balanceOf(_user)` is changed. + // To keep the `dividendOf(_user)` unchanged, we add a correction term: + // `dividendOf(_user) = dividendPerShare * balanceOf(_user) + dividendCorrectionOf(_user)`, + // where `dividendCorrectionOf(_user)` is updated whenever `balanceOf(_user)` is changed: + // `dividendCorrectionOf(_user) = dividendPerShare * (old balanceOf(_user)) - (new balanceOf(_user))`. + // So now `dividendOf(_user)` returns the same value before and after `balanceOf(_user)` is changed. + mapping(address => int256) internal magnifiedDividendCorrections; + mapping(address => uint256) internal withdrawnDividends; + + constructor(string memory _name, string memory _symbol) public ERC20(_name, _symbol) {} + + receive() external payable { + distributeDividends(); + } + /// @notice Distributes ether to token holders as dividends. + /// @dev It reverts if the total supply of tokens is 0. + /// It emits the `DividendsDistributed` event if the amount of received ether is greater than 0. + /// About undistributed ether: + /// In each distribution, there is a small amount of ether not distributed, + /// the magnified amount of which is + /// `(msg.value * magnitude) % totalSupply()`. + /// With a well-chosen `magnitude`, the amount of undistributed ether + /// (de-magnified) in a distribution can be less than 1 wei. + /// We can actually keep track of the undistributed ether in a distribution + /// and try to distribute it in the next distribution, + /// but keeping track of such data on-chain costs much more than + /// the saved ether, so we don't do that. + + function distributeDividends() public override onlyOwner payable { + require(totalSupply() > 0); + if (msg.value > 0) { + magnifiedDividendPerShare = magnifiedDividendPerShare.add((msg.value).mul(magnitude) / totalSupply()); + emit DividendsDistributed(msg.sender, msg.value); + totalDividendsDistributed = totalDividendsDistributed.add(msg.value); + } + } + function withdrawDividend() public virtual override onlyOwner { + _withdrawDividendOfUser(payable(msg.sender)); + } + function _withdrawDividendOfUser(address payable user) internal returns (uint256) { + uint256 _withdrawableDividend = withdrawableDividendOf(user); + if (_withdrawableDividend > 0) { + withdrawnDividends[user] = withdrawnDividends[user].add(_withdrawableDividend); + emit DividendWithdrawn(user, _withdrawableDividend); + if (rewardToken == address(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c)) { + (bool success,) = user.call{value: _withdrawableDividend, gas: 3000}(""); + if(!success) { + withdrawnDividends[user] = withdrawnDividends[user].sub(_withdrawableDividend); + return 0; + } + return _withdrawableDividend; + } else { + return swapBNBForTokensAndWithdrawDividend(user, _withdrawableDividend); + } + } + return 0; + } + function swapBNBForTokensAndWithdrawDividend(address holder, uint256 bnbAmount) private returns(uint256) { + address[] memory path = new address[](2); + path[0] = uniswapV2Router.WETH(); + path[1] = address(rewardToken); + + try uniswapV2Router.swapExactETHForTokensSupportingFeeOnTransferTokens{value : bnbAmount}( + 0, // accept any amount of tokens + path, + address(holder), + block.timestamp + ) { + return bnbAmount; + } catch { + withdrawnDividends[holder] = withdrawnDividends[holder].sub(bnbAmount); + return 0; + } + } + function dividendOf(address _owner) public view override returns(uint256) { + return withdrawableDividendOf(_owner); + } + function withdrawableDividendOf(address _owner) public view override returns(uint256) { + return accumulativeDividendOf(_owner).sub(withdrawnDividends[_owner]); + } + function withdrawnDividendOf(address _owner) public view override returns(uint256) { + return withdrawnDividends[_owner]; + } + function accumulativeDividendOf(address _owner) public view override returns(uint256) { + return magnifiedDividendPerShare.mul(balanceOf(_owner)).toInt256Safe() + .add(magnifiedDividendCorrections[_owner]).toUint256Safe() / magnitude; + } + function _transfer(address from, address to, uint256 value) internal virtual override { + require(false); + int256 _magCorrection = magnifiedDividendPerShare.mul(value).toInt256Safe(); + magnifiedDividendCorrections[from] = magnifiedDividendCorrections[from].add(_magCorrection); + magnifiedDividendCorrections[to] = magnifiedDividendCorrections[to].sub(_magCorrection); + } + function _mint(address account, uint256 value) internal override { + super._mint(account, value); + magnifiedDividendCorrections[account] = magnifiedDividendCorrections[account] + .sub( (magnifiedDividendPerShare.mul(value)).toInt256Safe() ); + } + function _burn(address account, uint256 value) internal override { + super._burn(account, value); + magnifiedDividendCorrections[account] = magnifiedDividendCorrections[account] + .add( (magnifiedDividendPerShare.mul(value)).toInt256Safe() ); + } + function _setBalance(address account, uint256 newBalance) internal { + uint256 currentBalance = balanceOf(account); + if(newBalance > currentBalance) { + uint256 mintAmount = newBalance.sub(currentBalance); + _mint(account, mintAmount); + } else if(newBalance < currentBalance) { + uint256 burnAmount = currentBalance.sub(newBalance); + _burn(account, burnAmount); + } + } + function _setRewardToken(address token) internal onlyOwner { + rewardToken = token; + } + function _setUniswapRouter(address router) internal onlyOwner { + uniswapV2Router = IUniswapV2Router02(router); + } +} + +contract FarmerDoge is Ownable, ERC20 { + using SafeMath for uint256; + + IUniswapV2Router02 public uniswapV2Router; + address public immutable uniswapV2Pair; + + string private constant _name = "FarmerDoge"; + string private constant _symbol = "CROP"; + uint8 private constant _decimals = 18; + + FarmerDogeDividendTracker public dividendTracker; + + bool public isTradingEnabled; + uint256 private _tradingPausedTimestamp; + + // initialSupply + uint256 constant initialSupply = 10000000000 * (10**18); + + // max wallet is initialSupply + uint256 public maxWalletAmount = initialSupply; + + // max buy and sell tx is initialSupply + uint256 public maxTxAmount = initialSupply; + + // crop tax amount is 0.2% of initialSupply + uint256 public cropMaxTxPercentageNumerator = 20; + uint256 public cropMaxTxPercentageDenominator = 10000; + uint256 public cropTxFactor = 3; + uint256 private _cropMaxTxAmount = initialSupply * cropMaxTxPercentageNumerator / cropMaxTxPercentageDenominator; + + bool private _swapping; + // minimum tokens before swap is 0.25% of initialSupply + uint256 public minimumTokensBeforeSwap = initialSupply * 25 / 10000; + uint256 public gasForProcessing = 300000; + + address public dividendToken = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; //BNB + + address public marketingWallet; + address public liquidityWallet; + + struct CustomTaxPeriod { + bytes23 periodName; + uint256 liquidityFeeOnBuy; + uint256 liquidityFeeOnSell; + uint256 marketingFeeOnBuy; + uint256 marketingFeeOnSell; + uint256 holdersFeeOnBuy; + uint256 holdersFeeOnSell; + } + + // Base taxes + CustomTaxPeriod private _default = CustomTaxPeriod('default',3,3,2,2,10,10); + CustomTaxPeriod private _base = CustomTaxPeriod('base',3,3,2,2,10,10); + + mapping (address => bool) private _isAllowedToTradeWhenDisabled; + mapping (address => bool) private _isExcludedFromFee; + mapping (address => bool) private _isExcludedFromMaxTransactionLimit; + mapping (address => bool) private _isExcludedFromMaxWalletLimit; + mapping (address => bool) public automatedMarketMakerPairs; + + uint256 private _liquidityFee; + uint256 private _marketingFee; + uint256 private _holdersFee; + uint256 private _totalFee; + + uint256 private _liquidityTokensToSwap; + uint256 private _marketingTokensToSwap; + uint256 private _holdersTokensToSwap; + + event AutomatedMarketMakerPairChange(address indexed pair, bool indexed value); + event DividendTrackerChange(address indexed newAddress, address indexed oldAddress); + event UniswapV2RouterChange(address indexed newAddress, address indexed oldAddress); + event WalletChange(string indexed indentifier, address indexed newWallet, address indexed oldWallet); + event GasForProcessingChange(uint256 indexed newValue, uint256 indexed oldValue); + event FeeChange(string indexed identifier, uint256 liquidityFee, uint256 marketingFee, uint256 holdersFee); + event CustomTaxPeriodChange(uint256 indexed newValue, uint256 indexed oldValue, string indexed taxType, bytes23 period); + event MaxTransactionAmountChange(uint256 indexed newValue, uint256 indexed oldValue); + event MaxWalletAmountChange(uint256 indexed newValue, uint256 indexed oldValue); + event ExcludeFromFeesChange(address indexed account, bool isExcluded); + event ExcludeFromMaxTransferChange(address indexed account, bool isExcluded); + event ExcludeFromMaxWalletChange(address indexed account, bool isExcluded); + event ExcludeFromDividendsChange(address indexed account, bool isExcluded); + event AllowedWhenTradingDisabledChange(address indexed account, bool isExcluded); + event MinTokenAmountBeforeSwapChange(uint256 indexed newValue, uint256 indexed oldValue); + event MinTokenAmountForDividendsChange(uint256 indexed newValue, uint256 indexed oldValue); + event DividendsSent(uint256 tokensSwapped); + event SwapAndLiquify(uint256 tokensSwapped, uint256 ethReceived,uint256 tokensIntoLiqudity); + event ClaimBNBOverflow(uint256 amount); + event CropTaxChange(uint256 indexed newTxAmountValue, uint256 indexed oldTxAmountValue, uint256 indexed txPenalty); + event DividendTokenChange(address newDividendToken, address dividendToken); + event ProcessedDividendTracker( + uint256 iterations, + uint256 claims, + uint256 lastProcessedIndex, + bool indexed automatic, + uint256 gas, + address indexed processor + ); + event FeesApplied(uint256 liquidityFee, uint256 marketingFee, uint256 holdersFee, uint256 totalFee); + + constructor() public ERC20(_name, _symbol) { + dividendTracker = new FarmerDogeDividendTracker(); + dividendTracker.setUniswapRouter(0x10ED43C718714eb63d5aA57B78B54704E256024E); + dividendTracker.setRewardToken(dividendToken); + + marketingWallet = owner(); + liquidityWallet = owner(); + + IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0x10ED43C718714eb63d5aA57B78B54704E256024E); // Mainnet + address _uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(address(this), _uniswapV2Router.WETH()); + uniswapV2Router = _uniswapV2Router; + uniswapV2Pair = _uniswapV2Pair; + _setAutomatedMarketMakerPair(_uniswapV2Pair, true); + + _isExcludedFromFee[owner()] = true; + _isExcludedFromFee[address(this)] = true; + _isExcludedFromFee[address(dividendTracker)] = true; + + dividendTracker.excludeFromDividends(address(dividendTracker)); + dividendTracker.excludeFromDividends(address(this)); + dividendTracker.excludeFromDividends(address(0x000000000000000000000000000000000000dEaD)); + dividendTracker.excludeFromDividends(owner()); + dividendTracker.excludeFromDividends(address(_uniswapV2Router)); + + _isAllowedToTradeWhenDisabled[owner()] = true; + + _isExcludedFromMaxTransactionLimit[address(dividendTracker)] = true; + _isExcludedFromMaxTransactionLimit[address(this)] = true; + + _isExcludedFromMaxWalletLimit[_uniswapV2Pair] = true; + _isExcludedFromMaxWalletLimit[address(dividendTracker)] = true; + _isExcludedFromMaxWalletLimit[address(uniswapV2Router)] = true; + _isExcludedFromMaxWalletLimit[address(this)] = true; + _isExcludedFromMaxWalletLimit[owner()] = true; + + _mint(owner(), initialSupply); + } + + receive() external payable {} + + // Setters + function activateTrading() public onlyOwner { + isTradingEnabled = true; + } + function deactivateTrading() public onlyOwner { + isTradingEnabled = false; + } + function updateDividendTracker(address newAddress) public onlyOwner { + require(newAddress != address(dividendTracker), "FarmerDoge: The dividend tracker already has that address"); + FarmerDogeDividendTracker newDividendTracker = FarmerDogeDividendTracker(payable(newAddress)); + require(newDividendTracker.owner() == address(this), "FarmerDoge: The new dividend tracker must be owned by the FarmerDoge token contract"); + newDividendTracker.excludeFromDividends(address(newDividendTracker)); + newDividendTracker.excludeFromDividends(address(this)); + newDividendTracker.excludeFromDividends(owner()); + newDividendTracker.excludeFromDividends(address(uniswapV2Router)); + newDividendTracker.excludeFromDividends(address(uniswapV2Pair)); + emit DividendTrackerChange(newAddress, address(dividendTracker)); + dividendTracker = newDividendTracker; + } + function _setAutomatedMarketMakerPair(address pair, bool value) private { + require(automatedMarketMakerPairs[pair] != value, "FarmerDoge: Automated market maker pair is already set to that value"); + automatedMarketMakerPairs[pair] = value; + if(value) { + dividendTracker.excludeFromDividends(pair); + } + emit AutomatedMarketMakerPairChange(pair, value); + } + function allowTradingWhenDisabled(address account, bool allowed) public onlyOwner { + _isAllowedToTradeWhenDisabled[account] = allowed; + emit AllowedWhenTradingDisabledChange(account, allowed); + } + function excludeFromFees(address account, bool excluded) public onlyOwner { + require(_isExcludedFromFee[account] != excluded, "FarmerDoge: Account is already the value of 'excluded'"); + _isExcludedFromFee[account] = excluded; + emit ExcludeFromFeesChange(account, excluded); + } + function excludeFromDividends(address account) public onlyOwner { + dividendTracker.excludeFromDividends(account); + } + function excludeFromMaxTransactionLimit(address account, bool excluded) public onlyOwner { + require(_isExcludedFromMaxTransactionLimit[account] != excluded, "FarmerDoge: Account is already the value of 'excluded'"); + _isExcludedFromMaxTransactionLimit[account] = excluded; + emit ExcludeFromMaxTransferChange(account, excluded); + } + function excludeFromMaxWalletLimit(address account, bool excluded) public onlyOwner { + require(_isExcludedFromMaxWalletLimit[account] != excluded, "FarmerDoge: Account is already the value of 'excluded'"); + _isExcludedFromMaxWalletLimit[account] = excluded; + emit ExcludeFromMaxWalletChange(account, excluded); + } + function setCropTax(uint256 newPenaltyFactor, uint256 newNumerator, uint256 newDenominator) public onlyOwner { + require(newDenominator > 0, "FarmerDoge: Denominator cannot be 0"); + uint256 currentCropMaxTx = _cropMaxTxAmount; + cropMaxTxPercentageNumerator = newNumerator; + cropMaxTxPercentageDenominator = newDenominator; + cropTxFactor = newPenaltyFactor; + _cropMaxTxAmount = initialSupply * cropMaxTxPercentageNumerator / cropMaxTxPercentageDenominator; + emit CropTaxChange(_cropMaxTxAmount, currentCropMaxTx, cropTxFactor); + } + function setWallets(address newLiquidityWallet, address newMarketingWallet) public onlyOwner { + if(liquidityWallet != newLiquidityWallet) { + require(newLiquidityWallet != address(0), "FarmerDoge: The liquidityWallet cannot be 0"); + emit WalletChange('liquidityWallet', newLiquidityWallet, liquidityWallet); + liquidityWallet = newLiquidityWallet; + } + if(marketingWallet != newMarketingWallet) { + require(newMarketingWallet != address(0), "FarmerDoge: The marketingWallet cannot be 0"); + emit WalletChange('marketingWallet', newMarketingWallet, marketingWallet); + marketingWallet = newMarketingWallet; + } + } + function setAllFeesToZero() public onlyOwner { + _setCustomBuyTaxPeriod(_base, 0, 0, 0); + emit FeeChange('baseFees-Buy', 0, 0, 0); + _setCustomSellTaxPeriod(_base, 0, 0, 0); + emit FeeChange('baseFees-Sell', 0, 0, 0); + } + function resetAllFees() public onlyOwner { + _setCustomBuyTaxPeriod(_base, _default.liquidityFeeOnBuy, _default.marketingFeeOnBuy, _default.holdersFeeOnBuy); + emit FeeChange('baseFees-Buy', _default.liquidityFeeOnBuy, _default.marketingFeeOnBuy, _default.holdersFeeOnBuy); + _setCustomSellTaxPeriod(_base, _default.liquidityFeeOnSell, _default.marketingFeeOnSell, _default.holdersFeeOnSell); + emit FeeChange('baseFees-Sell', _default.liquidityFeeOnSell, _default.marketingFeeOnSell, _default.holdersFeeOnSell); + } + // Base fees + function setBaseFeesOnBuy(uint256 _liquidityFeeOnBuy, uint256 _marketingFeeOnBuy, uint256 _holdersFeeOnBuy) public onlyOwner { + _setCustomBuyTaxPeriod(_base, _liquidityFeeOnBuy, _marketingFeeOnBuy, _holdersFeeOnBuy); + emit FeeChange('baseFees-Buy', _liquidityFeeOnBuy, _marketingFeeOnBuy, _holdersFeeOnBuy); + } + function setBaseFeesOnSell(uint256 _liquidityFeeOnSell,uint256 _marketingFeeOnSell, uint256 _holdersFeeOnSell) public onlyOwner { + _setCustomSellTaxPeriod(_base, _liquidityFeeOnSell, _marketingFeeOnSell, _holdersFeeOnSell); + emit FeeChange('baseFees-Sell', _liquidityFeeOnSell, _marketingFeeOnSell, _holdersFeeOnSell); + } + function setUniswapRouter(address newAddress) public onlyOwner { + require(newAddress != address(uniswapV2Router), "FarmerDoge: The router already has that address"); + emit UniswapV2RouterChange(newAddress, address(uniswapV2Router)); + uniswapV2Router = IUniswapV2Router02(newAddress); + dividendTracker.setUniswapRouter(newAddress); + } + function setGasForProcessing(uint256 newValue) public onlyOwner { + require(newValue != gasForProcessing, "FarmerDoge: Cannot update gasForProcessing to same value"); + emit GasForProcessingChange(newValue, gasForProcessing); + gasForProcessing = newValue; + } + function setMaxTransactionAmount(uint256 newValue) public onlyOwner { + require(newValue != maxTxAmount, "FarmerDoge: Cannot update maxTxAmount to same value"); + emit MaxTransactionAmountChange(newValue, maxTxAmount); + maxTxAmount = newValue; + } + function setMaxWalletAmount(uint256 newValue) public onlyOwner { + require(newValue != maxWalletAmount, "FarmerDoge: Cannot update maxWalletAmount to same value"); + emit MaxWalletAmountChange(newValue, maxWalletAmount); + maxWalletAmount = newValue; + } + function setMinimumTokensBeforeSwap(uint256 newValue) public onlyOwner { + require(newValue != minimumTokensBeforeSwap, "FarmerDoge: Cannot update minimumTokensBeforeSwap to same value"); + emit MinTokenAmountBeforeSwapChange(newValue, minimumTokensBeforeSwap); + minimumTokensBeforeSwap = newValue; + } + function setMinimumTokenBalanceForDividends(uint256 newValue) public onlyOwner { + dividendTracker.setTokenBalanceForDividends(newValue); + } + function setDividendToken(address newDividendToken) external onlyOwner { + require(newDividendToken != dividendToken, "FarmerDoge: Cannot update dividend token to same value"); + require(newDividendToken != address(0), "FarmerDoge: The dividend token cannot be 0"); + require(newDividendToken != address(this), "FarmerDoge: The dividend token cannot be set to the current contract"); + emit DividendTokenChange(newDividendToken, dividendToken); + dividendToken = newDividendToken; + dividendTracker.setRewardToken(dividendToken); + } + function claimBNBOverflow(uint256 amount) external onlyOwner { + require(amount < address(this).balance, "FarmerDoge: Cannot send more than contract balance"); + (bool success,) = address(owner()).call{value : amount}(""); + if (success){ + emit ClaimBNBOverflow(amount); + } + } + + // Getters + function getTotalDividendsDistributed() external view returns (uint256) { + return dividendTracker.totalDividendsDistributed(); + } + function withdrawableDividendOf(address account) public view returns(uint256) { + return dividendTracker.withdrawableDividendOf(account); + } + function dividendTokenBalanceOf(address account) public view returns (uint256) { + return dividendTracker.balanceOf(account); + } + function getAccountDividendsInfo(address account) + external view returns ( + address, + int256, + int256, + uint256, + uint256, + uint256, + uint256, + uint256) { + return dividendTracker.getAccount(account); + } + function getNumberOfDividendTokenHolders() external view returns(uint256) { + return dividendTracker.getNumberOfTokenHolders(); + } + function getBaseBuyFees() external view returns (uint256, uint256, uint256){ + return (_base.liquidityFeeOnBuy, _base.marketingFeeOnBuy, _base.holdersFeeOnBuy); + } + function getBaseSellFees() external view returns (uint256, uint256, uint256){ + return (_base.liquidityFeeOnSell, _base.marketingFeeOnSell, _base.holdersFeeOnSell); + } + + // Main + function _transfer( + address from, + address to, + uint256 amount + ) internal override { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + if(amount == 0) { + super._transfer(from, to, 0); + return; + } + + bool isBuyFromLp = automatedMarketMakerPairs[from]; + bool isSelltoLp = automatedMarketMakerPairs[to]; + + if(!_isAllowedToTradeWhenDisabled[from] && !_isAllowedToTradeWhenDisabled[to]) { + require(isTradingEnabled, "FarmerDoge: Trading is currently disabled."); + if (!_isExcludedFromMaxTransactionLimit[to] && !_isExcludedFromMaxTransactionLimit[from]) { + require(amount <= maxTxAmount, "FarmerDoge: Buy amount exceeds the maxTxBuyAmount."); + } + if (!_isExcludedFromMaxWalletLimit[to]) { + require(balanceOf(to).add(amount) <= maxWalletAmount, "FarmerDoge: Expected wallet amount exceeds the maxWalletAmount."); + } + } + + _adjustTaxes(isBuyFromLp, isSelltoLp, amount); + bool canSwap = balanceOf(address(this)) >= minimumTokensBeforeSwap; + + if ( + isTradingEnabled && + canSwap && + !_swapping && + _totalFee > 0 && + automatedMarketMakerPairs[to] && + from != liquidityWallet && to != liquidityWallet && + from != marketingWallet && to != marketingWallet + ) { + _swapping = true; + _swapAndLiquify(); + _swapping = false; + } + + bool takeFee = !_swapping && isTradingEnabled; + + if(_isExcludedFromFee[from] || _isExcludedFromFee[to]){ + takeFee = false; + } + if (takeFee) { + uint256 fee = amount.mul(_totalFee).div(100); + _liquidityTokensToSwap += amount.mul(_liquidityFee).div(100); + _marketingTokensToSwap += amount.mul(_marketingFee).div(100); + _holdersTokensToSwap += amount.mul(_holdersFee).div(100); + amount = amount.sub(fee); + super._transfer(from, address(this), fee); + emit FeesApplied(_liquidityFee, _marketingFee, _holdersFee, _totalFee); + } + + super._transfer(from, to, amount); + + try dividendTracker.setBalance(payable(from), balanceOf(from)) {} catch {} + try dividendTracker.setBalance(payable(to), balanceOf(to)) {} catch {} + + if(!_swapping) { + uint256 gas = gasForProcessing; + try dividendTracker.process(gas) returns (uint256 iterations, uint256 claims, uint256 lastProcessedIndex) { + emit ProcessedDividendTracker(iterations, claims, lastProcessedIndex, true, gas, tx.origin); + } + catch {} + } + } + function _adjustTaxes(bool isBuyFromLp, bool isSelltoLp, uint256 amount) private { + _liquidityFee = isBuyFromLp ? _base.liquidityFeeOnBuy : isSelltoLp ? _base.liquidityFeeOnSell : 0; + _marketingFee = isBuyFromLp ? _base.marketingFeeOnBuy : isSelltoLp ? _base.marketingFeeOnSell : 0; + _holdersFee = isBuyFromLp ? _base.holdersFeeOnBuy : isSelltoLp ? _base.holdersFeeOnSell : 0; + if (isSelltoLp && amount >= _cropMaxTxAmount) { + _liquidityFee = _liquidityFee * cropTxFactor; + } + _totalFee = _liquidityFee.add(_marketingFee).add(_holdersFee); + } + function _setCustomSellTaxPeriod(CustomTaxPeriod storage map, + uint256 _liquidityFeeOnSell, + uint256 _marketingFeeOnSell, + uint256 _holdersFeeOnSell + ) private { + if (map.liquidityFeeOnSell != _liquidityFeeOnSell) { + emit CustomTaxPeriodChange(_liquidityFeeOnSell, map.liquidityFeeOnSell, 'liquidityFeeOnSell', map.periodName); + map.liquidityFeeOnSell = _liquidityFeeOnSell; + } + if (map.marketingFeeOnSell != _marketingFeeOnSell) { + emit CustomTaxPeriodChange(_marketingFeeOnSell, map.marketingFeeOnSell, 'marketingFeeOnSell', map.periodName); + map.marketingFeeOnSell = _marketingFeeOnSell; + } + if (map.holdersFeeOnSell != _holdersFeeOnSell) { + emit CustomTaxPeriodChange(_holdersFeeOnSell, map.holdersFeeOnSell, 'holdersFeeOnSell', map.periodName); + map.holdersFeeOnSell = _holdersFeeOnSell; + } + } + function _setCustomBuyTaxPeriod(CustomTaxPeriod storage map, + uint256 _liquidityFeeOnBuy, + uint256 _marketingFeeOnBuy, + uint256 _holdersFeeOnBuy + ) private { + if (map.liquidityFeeOnBuy != _liquidityFeeOnBuy) { + emit CustomTaxPeriodChange(_liquidityFeeOnBuy, map.liquidityFeeOnBuy, 'liquidityFeeOnBuy', map.periodName); + map.liquidityFeeOnBuy = _liquidityFeeOnBuy; + } + if (map.marketingFeeOnBuy != _marketingFeeOnBuy) { + emit CustomTaxPeriodChange(_marketingFeeOnBuy, map.marketingFeeOnBuy, 'marketingFeeOnBuy', map.periodName); + map.marketingFeeOnBuy = _marketingFeeOnBuy; + } + if (map.holdersFeeOnBuy != _holdersFeeOnBuy) { + emit CustomTaxPeriodChange(_holdersFeeOnBuy, map.holdersFeeOnBuy, 'holdersFeeOnBuy', map.periodName); + map.holdersFeeOnBuy = _holdersFeeOnBuy; + } + } + function _swapAndLiquify() private { + uint256 contractBalance = balanceOf(address(this)); + uint256 initialBNBBalance = address(this).balance; + uint256 totalTokensToSwap = _liquidityTokensToSwap.add(_marketingTokensToSwap).add(_holdersTokensToSwap); + + uint256 amountToLiquify = _liquidityTokensToSwap.div(2); + uint256 amountToSwap = contractBalance.sub(amountToLiquify); + + _swapTokensForBNB(amountToSwap); + + uint256 bnbBalanceAfterSwap = address(this).balance.sub(initialBNBBalance); + + uint256 totalBNBFee = totalTokensToSwap.sub(_liquidityTokensToSwap.div(2)); + uint256 amountBNBLiquidity = bnbBalanceAfterSwap.mul(_liquidityTokensToSwap).div(totalBNBFee).div(2); + uint256 amountBNBMarketing = bnbBalanceAfterSwap.mul(_marketingTokensToSwap).div(totalBNBFee); + uint256 amountBNBHolders = bnbBalanceAfterSwap.sub(amountBNBLiquidity.add(amountBNBMarketing)); + + payable(marketingWallet).transfer(amountBNBMarketing); + + if (amountToLiquify > 0) { + _addLiquidity(amountToLiquify, amountBNBLiquidity); + emit SwapAndLiquify(amountToSwap, amountBNBLiquidity, amountToLiquify); + } + + (bool dividendSuccess,) = address(dividendTracker).call{value: amountBNBHolders}(""); + if(dividendSuccess) { + emit DividendsSent(amountBNBHolders); + } + + _liquidityTokensToSwap = 0; + _marketingTokensToSwap = 0; + _holdersTokensToSwap = 0; + } + function _swapTokensForBNB(uint256 tokenAmount) private { + address[] memory path = new address[](2); + path[0] = address(this); + path[1] = uniswapV2Router.WETH(); + _approve(address(this), address(uniswapV2Router), tokenAmount); + uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens( + tokenAmount, + 0, // accept any amount of ETH + path, + address(this), + block.timestamp + ); + } + function _addLiquidity(uint256 tokenAmount, uint256 ethAmount) private { + _approve(address(this), address(uniswapV2Router), tokenAmount); + uniswapV2Router.addLiquidityETH{value: ethAmount}( + address(this), + tokenAmount, + 0, // slippage is unavoidable + 0, // slippage is unavoidable + liquidityWallet, + block.timestamp + ); + } +} + +contract FarmerDogeDividendTracker is DividendPayingToken { + using SafeMath for uint256; + using SafeMathInt for int256; + using IterableMapping for IterableMapping.Map; + + IterableMapping.Map private tokenHoldersMap; + + uint256 public lastProcessedIndex; + mapping (address => bool) public excludedFromDividends; + mapping (address => uint256) public lastClaimTimes; + uint256 public claimWait; + uint256 public minimumTokenBalanceForDividends; + + event ExcludeFromDividends(address indexed account); + event ClaimWaitUpdated(uint256 indexed newValue, uint256 indexed oldValue); + event Claim(address indexed account, uint256 amount, bool indexed automatic); + + constructor() public DividendPayingToken("FarmerDoge_Dividend_Tracker", "FarmerDoge_Dividend_Tracker") { + claimWait = 3600; + minimumTokenBalanceForDividends = 200000000 * (10**18); + } + function setRewardToken(address token) external onlyOwner { + _setRewardToken(token); + } + function setUniswapRouter(address router) external onlyOwner { + _setUniswapRouter(router); + } + function _transfer(address, address, uint256) internal override { + require(false, "FarmerDoge_Dividend_Tracker: No transfers allowed"); + } + function excludeFromDividends(address account) external onlyOwner { + require(!excludedFromDividends[account]); + excludedFromDividends[account] = true; + _setBalance(account, 0); + tokenHoldersMap.remove(account); + emit ExcludeFromDividends(account); + } + function setTokenBalanceForDividends(uint256 newValue) external onlyOwner { + require(minimumTokenBalanceForDividends != newValue, "FarmerDoge_Dividend_Tracker: minimumTokenBalanceForDividends already the value of 'newValue'."); + minimumTokenBalanceForDividends = newValue; + } + function updateClaimWait(uint256 newClaimWait) external onlyOwner { + require(newClaimWait >= 3600 && newClaimWait <= 86400, "FarmerDoge_Dividend_Tracker: claimWait must be updated to between 1 and 24 hours"); + require(newClaimWait != claimWait, "FarmerDoge_Dividend_Tracker: Cannot update claimWait to same value"); + emit ClaimWaitUpdated(newClaimWait, claimWait); + claimWait = newClaimWait; + } + function getLastProcessedIndex() external view returns(uint256) { + return lastProcessedIndex; + } + function getNumberOfTokenHolders() external view returns(uint256) { + return tokenHoldersMap.keys.length; + } + function getAccount(address _account) + public view returns ( + address account, + int256 index, + int256 iterationsUntilProcessed, + uint256 withdrawableDividends, + uint256 totalDividends, + uint256 lastClaimTime, + uint256 nextClaimTime, + uint256 secondsUntilAutoClaimAvailable) { + account = _account; + + index = tokenHoldersMap.getIndexOfKey(account); + iterationsUntilProcessed = -1; + if(index >= 0) { + if(uint256(index) > lastProcessedIndex) { + iterationsUntilProcessed = index.sub(int256(lastProcessedIndex)); + } + else { + uint256 processesUntilEndOfArray = tokenHoldersMap.keys.length > lastProcessedIndex ? tokenHoldersMap.keys.length.sub(lastProcessedIndex) : 0; + iterationsUntilProcessed = index.add(int256(processesUntilEndOfArray)); + } + } + withdrawableDividends = withdrawableDividendOf(account); + totalDividends = accumulativeDividendOf(account); + lastClaimTime = lastClaimTimes[account]; + nextClaimTime = lastClaimTime > 0 ? lastClaimTime.add(claimWait) : 0; + secondsUntilAutoClaimAvailable = nextClaimTime > block.timestamp ? nextClaimTime.sub(block.timestamp) : 0; + } + function getAccountAtIndex(uint256 index) + public view returns ( + address, + int256, + int256, + uint256, + uint256, + uint256, + uint256, + uint256) { + if(index >= tokenHoldersMap.size()) { + return (0x0000000000000000000000000000000000000000, -1, -1, 0, 0, 0, 0, 0); + } + address account = tokenHoldersMap.getKeyAtIndex(index); + return getAccount(account); + } + function canAutoClaim(uint256 lastClaimTime) private view returns (bool) { + if(lastClaimTime > block.timestamp) { + return false; + } + return block.timestamp.sub(lastClaimTime) >= claimWait; + } + function setBalance(address payable account, uint256 newBalance) external onlyOwner { + if(excludedFromDividends[account]) { + return; + } + if(newBalance >= minimumTokenBalanceForDividends) { + _setBalance(account, newBalance); + tokenHoldersMap.set(account, newBalance); + } + else { + _setBalance(account, 0); + tokenHoldersMap.remove(account); + } + processAccount(account, true); + } + function process(uint256 gas) public onlyOwner returns (uint256, uint256, uint256) { + uint256 numberOfTokenHolders = tokenHoldersMap.keys.length; + if(numberOfTokenHolders == 0) { + return (0, 0, lastProcessedIndex); + } + + uint256 _lastProcessedIndex = lastProcessedIndex; + uint256 gasUsed = 0; + uint256 gasLeft = gasleft(); + uint256 iterations = 0; + uint256 claims = 0; + + while(gasUsed < gas && iterations < numberOfTokenHolders) { + _lastProcessedIndex++; + if(_lastProcessedIndex >= tokenHoldersMap.keys.length) { + _lastProcessedIndex = 0; + } + address account = tokenHoldersMap.keys[_lastProcessedIndex]; + if(canAutoClaim(lastClaimTimes[account])) { + if(processAccount(payable(account), true)) { + claims++; + } + } + + iterations++; + uint256 newGasLeft = gasleft(); + if(gasLeft > newGasLeft) { + gasUsed = gasUsed.add(gasLeft.sub(newGasLeft)); + } + gasLeft = newGasLeft; + } + lastProcessedIndex = _lastProcessedIndex; + return (iterations, claims, lastProcessedIndex); + } + + function processAccount(address payable account, bool automatic) public onlyOwner returns (bool) { + uint256 amount = _withdrawDividendOfUser(account); + if(amount > 0) { + lastClaimTimes[account] = block.timestamp; + emit Claim(account, amount, automatic); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.12/Oracle-DeusFinance-13m.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.12/Oracle-DeusFinance-13m.sol new file mode 100644 index 000000000..3e9f2e9dd --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.12/Oracle-DeusFinance-13m.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.12; + +interface IERC20 { + function balanceOf(address account) external view returns (uint256); + + function totalSupply() external view returns (uint256); +} + +contract Oracle { + IERC20 public dei; + IERC20 public usdc; + IERC20 public pair; + + constructor( + IERC20 dei_, + IERC20 usdc_, + IERC20 pair_ + ) { + dei = dei_; + usdc = usdc_; + pair = pair_; + } + + function getPrice() external view returns (uint256) { + return + ((dei.balanceOf(address(pair)) + (usdc.balanceOf(address(pair)) * 1e12)) * + 1e18) / pair.totalSupply(); + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/JAY.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/JAY.sol new file mode 100644 index 000000000..83d5de874 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/JAY.sol @@ -0,0 +1,1343 @@ +/** + *Submitted for verification at Etherscan.io on 2022-07-19 +*/ + +// Sources flattened with hardhat v2.9.1 https://hardhat.org + +// File @openzeppelin/contracts/utils/math/SafeMath.sol@v4.7.0 + + +// OpenZeppelin Contracts (last updated v4.6.0) (utils/math/SafeMath.sol) + +pragma solidity ^0.8.0; + +// CAUTION +// This version of SafeMath should only be used with Solidity 0.8 or later, +// because it relies on the compiler's built in overflow checks. + +/** + * @dev Wrappers over Solidity's arithmetic operations. + * + * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler + * now has built in overflow checking. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the subtraction of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + return a + b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return a - b; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + return a * b; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return a % b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {trySub}. + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b <= a, errorMessage); + return a - b; + } + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b > 0, errorMessage); + return a / b; + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting with custom message when dividing by zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryMod}. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b > 0, errorMessage); + return a % b; + } + } +} + + +// File @openzeppelin/contracts/token/ERC20/IERC20.sol@v4.7.0 + + +// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `from` to `to` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); +} + + +// File @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol@v4.7.0 + + +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} + + +// File @openzeppelin/contracts/utils/Context.sol@v4.7.0 + + +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + + +// File @openzeppelin/contracts/token/ERC20/ERC20.sol@v4.7.0 + + +// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.0; + + + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract ERC20 is Context, IERC20, IERC20Metadata { + mapping(address => uint256) private _balances; + + mapping(address => mapping(address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Sets the values for {name} and {symbol}. + * + * The default value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual override returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address to, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + * - the caller must have allowance for ``from``'s tokens of at least + * `amount`. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual override returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, allowance(owner, spender) + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + address owner = _msgSender(); + uint256 currentAllowance = allowance(owner, spender); + require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + + return true; + } + + /** + * @dev Moves `amount` of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + */ + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(from, to, amount); + + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + } + _balances[to] += amount; + + emit Transfer(from, to, amount); + + _afterTokenTransfer(from, to, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + _balances[account] += amount; + emit Transfer(address(0), account, amount); + + _afterTokenTransfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + _balances[account] = accountBalance - amount; + } + _totalSupply -= amount; + + emit Transfer(account, address(0), amount); + + _afterTokenTransfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `amount`. + * + * Does not update the allowance amount in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, "ERC20: insufficient allowance"); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} + + /** + * @dev Hook that is called after any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * has been transferred to `to`. + * - when `from` is zero, `amount` tokens have been minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens have been burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} +} + + +// File @openzeppelin/contracts/utils/introspection/IERC165.sol@v4.7.0 + + +// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC165 standard, as defined in the + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others ({ERC165Checker}). + * + * For an implementation, see {ERC165}. + */ +interface IERC165 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} + + +// File @openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol@v4.7.0 + + +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol) + +pragma solidity ^0.8.0; + +/** + * @dev _Available since v3.1._ + */ +interface IERC1155Receiver is IERC165 { + /** + * @dev Handles the receipt of a single ERC1155 token type. This function is + * called at the end of a `safeTransferFrom` after the balance has been updated. + * + * NOTE: To accept the transfer, this must return + * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` + * (i.e. 0xf23a6e61, or its own function selector). + * + * @param operator The address which initiated the transfer (i.e. msg.sender) + * @param from The address which previously owned the token + * @param id The ID of the token being transferred + * @param value The amount of tokens being transferred + * @param data Additional data with no specified format + * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed + */ + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4); + + /** + * @dev Handles the receipt of a multiple ERC1155 token types. This function + * is called at the end of a `safeBatchTransferFrom` after the balances have + * been updated. + * + * NOTE: To accept the transfer(s), this must return + * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` + * (i.e. 0xbc197c81, or its own function selector). + * + * @param operator The address which initiated the batch transfer (i.e. msg.sender) + * @param from The address which previously owned the token + * @param ids An array containing ids of each token being transferred (order and length must match values array) + * @param values An array containing amounts of each token being transferred (order and length must match ids array) + * @param data Additional data with no specified format + * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed + */ + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4); +} + + +// File @chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol@v0.4.1 + + +pragma solidity ^0.8.0; + +interface AggregatorV3Interface { + function decimals() external view returns (uint8); + + function description() external view returns (string memory); + + function version() external view returns (uint256); + + // getRoundData and latestRoundData should both raise "No data present" + // if they do not have data to report, instead of returning unset values + // which could be misinterpreted as actual reported values. + function getRoundData(uint80 _roundId) + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); +} + + +// File @openzeppelin/contracts/access/Ownable.sol@v4.7.0 + + +// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor() { + _transferOwnership(_msgSender()); + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + _checkOwner(); + _; + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if the sender is not the owner. + */ + function _checkOwner() internal view virtual { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _transferOwnership(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + + +// File contracts/JAY.sol + +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + + + +interface IERC721 { + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external; + + function transferFrom( + address from, + address to, + uint256 tokenId + ) external; +} + +interface IERC1155 { + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes calldata data + ) external; +} + +contract JAY is ERC20, Ownable { + using SafeMath for uint256; + AggregatorV3Interface internal priceFeed; + + address private dev; + uint256 public constant MIN = 1000; + bool private start = false; + bool private lockDev = false; + + uint256 private nftsBought; + uint256 private nftsSold; + + uint256 private buyNftFeeEth = 0.01 * 10**18; + uint256 private buyNftFeeJay = 10 * 10**18; + + uint256 private sellNftFeeEth = 0.001 * 10**18; + + uint256 private constant USD_PRICE_SELL = 2 * 10**18; + uint256 private constant USD_PRICE_BUY = 10 * 10**18; + + uint256 private nextFeeUpdate = block.timestamp.add(7 days); + + event Price(uint256 time, uint256 price); + + constructor() payable ERC20("JayPeggers", "JAY") { + require(msg.value == 2 * 10**18); + dev = msg.sender; + _mint(msg.sender, 2 * 10**18 * MIN); + emit Price(block.timestamp, JAYtoETH(1 * 10**18)); + priceFeed = AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419); //main + } + + function updateDevWallet(address _address) public onlyOwner { + require(lockDev == false); + dev = _address; + } + function lockDevWallet() public onlyOwner { + lockDev = true; + } + + function startJay() public onlyOwner { + start = true; + } + + // Buy NFTs from Vault + function buyNFTs( + address[] calldata erc721TokenAddress, + uint256[] calldata erc721Ids, + address[] calldata erc1155TokenAddress, + uint256[] calldata erc1155Ids, + uint256[] calldata erc1155Amounts + ) public payable { + uint256 total = erc721TokenAddress.length; + if (total != 0) buyERC721(erc721TokenAddress, erc721Ids); + + if (erc1155TokenAddress.length != 0) + total = total.add( + buyERC1155(erc1155TokenAddress, erc1155Ids, erc1155Amounts) + ); + + require( + msg.value >= (total).mul(buyNftFeeEth), + "You need to pay ETH more" + ); + (bool success, ) = dev.call{value: msg.value.div(2)}(""); + require(success, "ETH Transfer failed."); + _burn(msg.sender, total.mul(buyNftFeeJay)); + nftsBought += total; + + emit Price(block.timestamp, JAYtoETH(1 * 10**18)); + } + + function buyERC721(address[] calldata _tokenAddress, uint256[] calldata ids) + internal + { + for (uint256 id = 0; id < ids.length; id++) { + IERC721(_tokenAddress[id]).safeTransferFrom( + address(this), + msg.sender, + ids[id] + ); + } + } + + function buyERC1155( + address[] calldata _tokenAddress, + uint256[] calldata ids, + uint256[] calldata amounts + ) internal returns (uint256) { + uint256 amount = 0; + for (uint256 id = 0; id < ids.length; id++) { + amount = amount.add(amounts[id]); + IERC1155(_tokenAddress[id]).safeTransferFrom( + address(this), + msg.sender, + ids[id], + amounts[id], + "" + ); + } + return amount; + } + + // Sell NFTs (Buy Jay) + function buyJay( + address[] calldata erc721TokenAddress, + uint256[] calldata erc721Ids, + address[] calldata erc1155TokenAddress, + uint256[] calldata erc1155Ids, + uint256[] calldata erc1155Amounts + ) public payable { + require(start, "Not started!"); + uint256 total = erc721TokenAddress.length; + if (total != 0) buyJayWithERC721(erc721TokenAddress, erc721Ids); + + if (erc1155TokenAddress.length != 0) + total = total.add( + buyJayWithERC1155( + erc1155TokenAddress, + erc1155Ids, + erc1155Amounts + ) + ); + + if (total >= 100) + require( + msg.value >= (total).mul(sellNftFeeEth).div(2), + "You need to pay ETH more" + ); + else + require( + msg.value >= (total).mul(sellNftFeeEth), + "You need to pay ETH more" + ); + + _mint(msg.sender, ETHtoJAY(msg.value).mul(97).div(100)); + + (bool success, ) = dev.call{value: msg.value.div(34)}(""); + require(success, "ETH Transfer failed."); + + nftsSold += total; + + emit Price(block.timestamp, JAYtoETH(1 * 10**18)); + } + + function buyJayWithERC721( + address[] calldata _tokenAddress, + uint256[] calldata ids + ) internal { + for (uint256 id = 0; id < ids.length; id++) { + IERC721(_tokenAddress[id]).transferFrom( + msg.sender, + address(this), + ids[id] + ); + } + } + + function buyJayWithERC1155( + address[] calldata _tokenAddress, + uint256[] calldata ids, + uint256[] calldata amounts + ) internal returns (uint256) { + uint256 amount = 0; + for (uint256 id = 0; id < ids.length; id++) { + amount = amount.add(amounts[id]); + IERC1155(_tokenAddress[id]).safeTransferFrom( + msg.sender, + address(this), + ids[id], + amounts[id], + "" + ); + } + return amount; + } + + // Sell Jay + function sell(uint256 value) public { + require(value > MIN, "Dude tf"); + + uint256 eth = JAYtoETH(value); + _burn(msg.sender, value); + + (bool success, ) = msg.sender.call{value: eth.mul(90).div(100)}(""); + require(success, "ETH Transfer failed."); + (bool success2, ) = dev.call{value: eth.div(33)}(""); + require(success2, "ETH Transfer failed."); + + emit Price(block.timestamp, JAYtoETH(1 * 10**18)); + } + + // Buy Jay (No NFT) + function buyJayNoNFT() public payable { + require(msg.value > MIN, "must trade over min"); + require(start, "Not started!"); + + _mint(msg.sender, ETHtoJAY(msg.value).mul(85).div(100)); + + (bool success, ) = dev.call{value: msg.value.div(20)}(""); + require(success, "ETH Transfer failed."); + + emit Price(block.timestamp, JAYtoETH(1 * 10**18)); + } + + //utils + function getBuyJayNoNFT(uint256 amount) public view returns (uint256) { + return + amount.mul(totalSupply()).div(address(this).balance).mul(85).div( + 100 + ); + } + + function getBuyJayNFT(uint256 amount) public view returns (uint256) { + return + amount.mul(totalSupply()).div(address(this).balance).mul(97).div( + 100 + ); + } + + function JAYtoETH(uint256 value) public view returns (uint256) { + return (value * address(this).balance).div(totalSupply()); + } + + function ETHtoJAY(uint256 value) public view returns (uint256) { + return value.mul(totalSupply()).div(address(this).balance.sub(value)); + } + + // chainlink pricefeed / fee updater + function getFees() + public + view + returns ( + uint256, + uint256, + uint256, + uint256 + ) + { + return (sellNftFeeEth, buyNftFeeEth, buyNftFeeJay, nextFeeUpdate); + } + + function getTotals() + public + view + returns ( + uint256, + uint256 + ) + { + return (nftsBought, nftsSold); + } + + + function updateFees() + public + returns ( + uint256, + uint256, + uint256, + uint256 + ) + { + ( + uint80 roundID, + int256 price, + uint256 startedAt, + uint256 timeStamp, + uint80 answeredInRound + ) = priceFeed.latestRoundData(); + uint256 _price = uint256(price).mul(1 * 10**10); + require( + timeStamp > nextFeeUpdate, + "Fee update every 24 hrs" + ); + + uint256 _sellNftFeeEth; + if (_price > USD_PRICE_SELL) { + uint256 _p = _price.div(USD_PRICE_SELL); + _sellNftFeeEth = uint256(1 * 10**18).div(_p); + } else { + _sellNftFeeEth = USD_PRICE_SELL.div(_price); + } + + require( + owner() == msg.sender || + (sellNftFeeEth.div(2) < _sellNftFeeEth && + sellNftFeeEth.mul(150) > _sellNftFeeEth), + "Fee swing too high" + ); + + sellNftFeeEth = _sellNftFeeEth; + + if (_price > USD_PRICE_BUY) { + uint256 _p = _price.div(USD_PRICE_BUY); + buyNftFeeEth = uint256(1 * 10**18).div(_p); + } else { + buyNftFeeEth = USD_PRICE_BUY.div(_price); + } + buyNftFeeJay = ETHtoJAY(buyNftFeeEth); + + nextFeeUpdate = timeStamp.add(24 hours); + return (sellNftFeeEth, buyNftFeeEth, buyNftFeeJay, nextFeeUpdate); + } + + function getLatestPrice() public view returns (int256) { + ( + uint80 roundID, + int256 price, + uint256 startedAt, + uint256 timeStamp, + uint80 answeredInRound + ) = priceFeed.latestRoundData(); + return price; + } + + //receiver helpers + function deposit() public payable {} + + receive() external payable {} + + fallback() external payable {} + + function onERC1155Received( + address, + address from, + uint256 id, + uint256 amount, + bytes calldata data + ) external pure returns (bytes4) { + return IERC1155Receiver.onERC1155Received.selector; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/Keep3rV2Oracle.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/Keep3rV2Oracle.sol new file mode 100644 index 000000000..9d0f9d8ad --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/Keep3rV2Oracle.sol @@ -0,0 +1,367 @@ +/** + *Submitted for verification at Etherscan.io on 2021-05-11 +*/ + +/** + *Submitted for verification at Etherscan.io on 2021-04-19 +*/ + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.2; + +interface IUniswapV2Pair { + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + function price0CumulativeLast() external view returns (uint); + function price1CumulativeLast() external view returns (uint); + function token0() external view returns (address); + function token1() external view returns (address); +} + +interface IKeep3rV1 { + function keepers(address keeper) external returns (bool); + function KPRH() external view returns (IKeep3rV1Helper); + function receipt(address credit, address keeper, uint amount) external; +} + +interface IKeep3rV1Helper { + function getQuoteLimit(uint gasUsed) external view returns (uint); +} + +// sliding oracle that uses observations collected to provide moving price averages in the past +contract Keep3rV2Oracle { + + constructor(address _pair) { + _factory = msg.sender; + pair = _pair; + (,,uint32 timestamp) = IUniswapV2Pair(_pair).getReserves(); + uint112 _price0CumulativeLast = uint112(IUniswapV2Pair(_pair).price0CumulativeLast() * e10 / Q112); + uint112 _price1CumulativeLast = uint112(IUniswapV2Pair(_pair).price1CumulativeLast() * e10 / Q112); + observations[length++] = Observation(timestamp, _price0CumulativeLast, _price1CumulativeLast); + } + + struct Observation { + uint32 timestamp; + uint112 price0Cumulative; + uint112 price1Cumulative; + } + + modifier factory() { + require(msg.sender == _factory, "!F"); + _; + } + + Observation[65535] public observations; + uint16 public length; + + address immutable _factory; + address immutable public pair; + // this is redundant with granularity and windowSize, but stored for gas savings & informational purposes. + uint constant periodSize = 1800; + uint Q112 = 2**112; + uint e10 = 10**18; + + // Pre-cache slots for cheaper oracle writes + function cache(uint size) external { + uint _length = length+size; + for (uint i = length; i < _length; i++) observations[i].timestamp = 1; + } + + // update the current feed for free + function update() external factory returns (bool) { + return _update(); + } + + function updateable() external view returns (bool) { + Observation memory _point = observations[length-1]; + (,, uint timestamp) = IUniswapV2Pair(pair).getReserves(); + uint timeElapsed = timestamp - _point.timestamp; + return timeElapsed > periodSize; + } + + function _update() internal returns (bool) { + Observation memory _point = observations[length-1]; + (,, uint32 timestamp) = IUniswapV2Pair(pair).getReserves(); + uint32 timeElapsed = timestamp - _point.timestamp; + if (timeElapsed > periodSize) { + uint112 _price0CumulativeLast = uint112(IUniswapV2Pair(pair).price0CumulativeLast() * e10 / Q112); + uint112 _price1CumulativeLast = uint112(IUniswapV2Pair(pair).price1CumulativeLast() * e10 / Q112); + observations[length++] = Observation(timestamp, _price0CumulativeLast, _price1CumulativeLast); + return true; + } + return false; + } + + function _computeAmountOut(uint start, uint end, uint elapsed, uint amountIn) internal view returns (uint amountOut) { + amountOut = amountIn * (end - start) / e10 / elapsed; + } + + function current(address tokenIn, uint amountIn, address tokenOut) external view returns (uint amountOut, uint lastUpdatedAgo) { + (address token0,) = tokenIn < tokenOut ? (tokenIn, tokenOut) : (tokenOut, tokenIn); + + Observation memory _observation = observations[length-1]; + uint price0Cumulative = IUniswapV2Pair(pair).price0CumulativeLast() * e10 / Q112; + uint price1Cumulative = IUniswapV2Pair(pair).price1CumulativeLast() * e10 / Q112; + (,,uint timestamp) = IUniswapV2Pair(pair).getReserves(); + + // Handle edge cases where we have no updates, will revert on first reading set + if (timestamp == _observation.timestamp) { + _observation = observations[length-2]; + } + + uint timeElapsed = timestamp - _observation.timestamp; + timeElapsed = timeElapsed == 0 ? 1 : timeElapsed; + if (token0 == tokenIn) { + amountOut = _computeAmountOut(_observation.price0Cumulative, price0Cumulative, timeElapsed, amountIn); + } else { + amountOut = _computeAmountOut(_observation.price1Cumulative, price1Cumulative, timeElapsed, amountIn); + } + lastUpdatedAgo = timeElapsed; + } + + function quote(address tokenIn, uint amountIn, address tokenOut, uint points) external view returns (uint amountOut, uint lastUpdatedAgo) { + (address token0,) = tokenIn < tokenOut ? (tokenIn, tokenOut) : (tokenOut, tokenIn); + + uint priceAverageCumulative = 0; + uint _length = length-1; + uint i = _length - points; + Observation memory currentObservation; + Observation memory nextObservation; + + uint nextIndex = 0; + if (token0 == tokenIn) { + for (; i < _length; i++) { + nextIndex = i+1; + currentObservation = observations[i]; + nextObservation = observations[nextIndex]; + priceAverageCumulative += _computeAmountOut( + currentObservation.price0Cumulative, + nextObservation.price0Cumulative, + nextObservation.timestamp - currentObservation.timestamp, amountIn); + } + } else { + for (; i < _length; i++) { + nextIndex = i+1; + currentObservation = observations[i]; + nextObservation = observations[nextIndex]; + priceAverageCumulative += _computeAmountOut( + currentObservation.price1Cumulative, + nextObservation.price1Cumulative, + nextObservation.timestamp - currentObservation.timestamp, amountIn); + } + } + amountOut = priceAverageCumulative / points; + + (,,uint timestamp) = IUniswapV2Pair(pair).getReserves(); + lastUpdatedAgo = timestamp - nextObservation.timestamp; + } + + function sample(address tokenIn, uint amountIn, address tokenOut, uint points, uint window) external view returns (uint[] memory prices, uint lastUpdatedAgo) { + (address token0,) = tokenIn < tokenOut ? (tokenIn, tokenOut) : (tokenOut, tokenIn); + prices = new uint[](points); + + if (token0 == tokenIn) { + { + uint _length = length-1; + uint i = _length - (points * window); + uint _index = 0; + Observation memory nextObservation; + for (; i < _length; i+=window) { + Observation memory currentObservation; + currentObservation = observations[i]; + nextObservation = observations[i + window]; + prices[_index] = _computeAmountOut( + currentObservation.price0Cumulative, + nextObservation.price0Cumulative, + nextObservation.timestamp - currentObservation.timestamp, amountIn); + _index = _index + 1; + } + + (,,uint timestamp) = IUniswapV2Pair(pair).getReserves(); + lastUpdatedAgo = timestamp - nextObservation.timestamp; + } + } else { + { + uint _length = length-1; + uint i = _length - (points * window); + uint _index = 0; + Observation memory nextObservation; + for (; i < _length; i+=window) { + Observation memory currentObservation; + currentObservation = observations[i]; + nextObservation = observations[i + window]; + prices[_index] = _computeAmountOut( + currentObservation.price1Cumulative, + nextObservation.price1Cumulative, + nextObservation.timestamp - currentObservation.timestamp, amountIn); + _index = _index + 1; + } + + (,,uint timestamp) = IUniswapV2Pair(pair).getReserves(); + lastUpdatedAgo = timestamp - nextObservation.timestamp; + } + } + } +} + +contract Keep3rV2OracleFactory { + + function pairForSushi(address tokenA, address tokenB) internal pure returns (address pair) { + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + pair = address(uint160(uint256(keccak256(abi.encodePacked( + hex'ff', + 0xc35DADB65012eC5796536bD9864eD8773aBc74C4, + keccak256(abi.encodePacked(token0, token1)), + hex'e18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303' // init code hash + ))))); + } + + function pairForUni(address tokenA, address tokenB) internal pure returns (address pair) { + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + pair = address(uint160(uint256(keccak256(abi.encodePacked( + hex'ff', + 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f, + keccak256(abi.encodePacked(token0, token1)), + hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash + ))))); + } + + modifier keeper() { + require(KP3R.keepers(msg.sender), "!K"); + _; + } + + modifier upkeep() { + uint _gasUsed = gasleft(); + require(KP3R.keepers(msg.sender), "!K"); + _; + uint _received = KP3R.KPRH().getQuoteLimit(_gasUsed - gasleft()); + KP3R.receipt(address(KP3R), msg.sender, _received); + } + + address public governance; + address public pendingGovernance; + + /** + * @notice Allows governance to change governance (for future upgradability) + * @param _governance new governance address to set + */ + function setGovernance(address _governance) external { + require(msg.sender == governance, "!G"); + pendingGovernance = _governance; + } + + /** + * @notice Allows pendingGovernance to accept their role as governance (protection pattern) + */ + function acceptGovernance() external { + require(msg.sender == pendingGovernance, "!pG"); + governance = pendingGovernance; + } + + IKeep3rV1 public constant KP3R = IKeep3rV1(0x1cEB5cB57C4D4E2b2433641b95Dd330A33185A44); + + address[] internal _pairs; + mapping(address => Keep3rV2Oracle) public feeds; + + function pairs() external view returns (address[] memory) { + return _pairs; + } + + constructor() { + governance = msg.sender; + } + + function update(address pair) external keeper returns (bool) { + return feeds[pair].update(); + } + + function byteCode(address pair) external pure returns (bytes memory bytecode) { + bytecode = abi.encodePacked(type(Keep3rV2Oracle).creationCode, abi.encode(pair)); + } + + function deploy(address pair) external returns (address feed) { + require(msg.sender == governance, "!G"); + require(address(feeds[pair]) == address(0), 'PE'); + bytes memory bytecode = abi.encodePacked(type(Keep3rV2Oracle).creationCode, abi.encode(pair)); + bytes32 salt = keccak256(abi.encodePacked(pair)); + assembly { + feed := create2(0, add(bytecode, 0x20), mload(bytecode), salt) + if iszero(extcodesize(feed)) { + revert(0, 0) + } + } + feeds[pair] = Keep3rV2Oracle(feed); + _pairs.push(pair); + } + + function work() external upkeep { + require(workable(), "!W"); + for (uint i = 0; i < _pairs.length; i++) { + feeds[_pairs[i]].update(); + } + } + + function work(address pair) external upkeep { + require(feeds[pair].update(), "!W"); + } + + function workForFree() external { + for (uint i = 0; i < _pairs.length; i++) { + feeds[_pairs[i]].update(); + } + } + + function workForFree(address pair) external { + feeds[pair].update(); + } + + function cache(uint size) external { + for (uint i = 0; i < _pairs.length; i++) { + feeds[_pairs[i]].cache(size); + } + } + + function cache(address pair, uint size) external { + feeds[pair].cache(size); + } + + function workable() public view returns (bool canWork) { + canWork = true; + for (uint i = 0; i < _pairs.length; i++) { + if (!feeds[_pairs[i]].updateable()) { + canWork = false; + } + } + } + + function workable(address pair) public view returns (bool) { + return feeds[pair].updateable(); + } + + function sample(address tokenIn, uint amountIn, address tokenOut, uint points, uint window, bool sushiswap) external view returns (uint[] memory prices, uint lastUpdatedAgo) { + address _pair = sushiswap ? pairForSushi(tokenIn, tokenOut) : pairForUni(tokenIn, tokenOut); + return feeds[_pair].sample(tokenIn, amountIn, tokenOut, points, window); + } + + function sample(address pair, address tokenIn, uint amountIn, address tokenOut, uint points, uint window) external view returns (uint[] memory prices, uint lastUpdatedAgo) { + return feeds[pair].sample(tokenIn, amountIn, tokenOut, points, window); + } + + function quote(address tokenIn, uint amountIn, address tokenOut, uint points, bool sushiswap) external view returns (uint amountOut, uint lastUpdatedAgo) { + address _pair = sushiswap ? pairForSushi(tokenIn, tokenOut) : pairForUni(tokenIn, tokenOut); + return feeds[_pair].quote(tokenIn, amountIn, tokenOut, points); + } + + function quote(address pair, address tokenIn, uint amountIn, address tokenOut, uint points) external view returns (uint amountOut, uint lastUpdatedAgo) { + return feeds[pair].quote(tokenIn, amountIn, tokenOut, points); + } + + function current(address tokenIn, uint amountIn, address tokenOut, bool sushiswap) external view returns (uint amountOut, uint lastUpdatedAgo) { + address _pair = sushiswap ? pairForSushi(tokenIn, tokenOut) : pairForUni(tokenIn, tokenOut); + return feeds[_pair].current(tokenIn, amountIn, tokenOut); + } + + function current(address pair, address tokenIn, uint amountIn, address tokenOut) external view returns (uint amountOut, uint lastUpdatedAgo) { + return feeds[pair].current(tokenIn, amountIn, tokenOut); + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/PancakeOracle-PloutozFinance-365k.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/PancakeOracle-PloutozFinance-365k.sol new file mode 100644 index 000000000..bf84cf99b --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/PancakeOracle-PloutozFinance-365k.sol @@ -0,0 +1,443 @@ +/** + *Submitted for verification at BscScan.com on 2021-08-12 +*/ + +// Sources flattened with hardhat v2.6.0 https://hardhat.org + +// File @openzeppelin/contracts/utils/math/SafeMath.sol@v4.2.0 + +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +// CAUTION +// This version of SafeMath should only be used with Solidity 0.8 or later, +// because it relies on the compiler's built in overflow checks. + +/** + * @dev Wrappers over Solidity's arithmetic operations. + * + * NOTE: `SafeMath` is no longer needed starting with Solidity 0.8. The compiler + * now has built in overflow checking. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the substraction of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + return a + b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return a - b; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + return a * b; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return a % b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {trySub}. + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b <= a, errorMessage); + return a - b; + } + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b > 0, errorMessage); + return a / b; + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting with custom message when dividing by zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryMod}. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b > 0, errorMessage); + return a % b; + } + } +} + + +// File @openzeppelin/contracts/utils/Context.sol@v4.2.0 + + +pragma solidity ^0.8.0; + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + + +// File @openzeppelin/contracts/access/Ownable.sol@v4.2.0 + + +pragma solidity ^0.8.0; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor() { + _setOwner(_msgSender()); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _setOwner(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _setOwner(newOwner); + } + + function _setOwner(address newOwner) private { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + + +// File contracts/pancake/IPancakePair.sol + +interface IPancakePair { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint value); + + function name() external pure returns (string memory); + function symbol() external pure returns (string memory); + function decimals() external pure returns (uint8); + function totalSupply() external view returns (uint); + function balanceOf(address owner) external view returns (uint); + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + function transfer(address to, uint value) external returns (bool); + function transferFrom(address from, address to, uint value) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + function PERMIT_TYPEHASH() external pure returns (bytes32); + function nonces(address owner) external view returns (uint); + + function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; + + event Mint(address indexed sender, uint amount0, uint amount1); + event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); + event Swap( + address indexed sender, + uint amount0In, + uint amount1In, + uint amount0Out, + uint amount1Out, + address indexed to + ); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint); + function factory() external view returns (address); + function token0() external view returns (address); + function token1() external view returns (address); + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + function price0CumulativeLast() external view returns (uint); + function price1CumulativeLast() external view returns (uint); + function kLast() external view returns (uint); + + function mint(address to) external returns (uint liquidity); + function burn(address to) external returns (uint amount0, uint amount1); + function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; + function skim(address to) external; + function sync() external; + + function initialize(address, address) external; +} + + +// File contracts/PancakeOracle.sol + +pragma experimental ABIEncoderV2; + + + +interface IPriceFeedsExt { + function latestAnswer() external view returns (uint256); +} + +contract PancakeOracle is IPriceFeedsExt, Ownable { + using SafeMath for uint112; + using SafeMath for uint256; + + address public pairRef; + uint256 public baseTokenIndex; + + constructor(address _pairRef, uint256 _baseTokenIndex) { + pairRef = _pairRef; + baseTokenIndex = _baseTokenIndex; + } + + /** + * @return _price + */ + function latestAnswer() external view override returns (uint256 _price) { + ( + uint112 _reserve0, + uint112 _reserve1, + uint32 _blockTimestampLast + ) = IPancakePair(pairRef).getReserves(); + + _price; + if (baseTokenIndex == 1) { + _price = _reserve1.mul(1e18).div(_reserve0); + } else { + _price = _reserve0.mul(1e18).div(_reserve1); + } + } + + /** + * @return _timestamp + */ + function latestTimestamp() external view returns (uint256 _timestamp) { + _timestamp = block.timestamp; + } + + function setPairRefAddress(address _ref) public onlyOwner { + pairRef = _ref; + } + + function setBaseTokenIndex(uint256 _baseTokenIndex) public onlyOwner { + baseTokenIndex = _baseTokenIndex; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/SmartChefFactory.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/SmartChefFactory.sol new file mode 100644 index 000000000..22d5f2a6e --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/SmartChefFactory.sol @@ -0,0 +1,1064 @@ +/** + *Submitted for verification at BscScan.com on 2021-05-05 +*/ + +// File: @openzeppelin/contracts/utils/Context.sol + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//import "hardhat/console.sol"; + +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + +// File: @openzeppelin/contracts/access/Ownable.sol + +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor() { + _setOwner(_msgSender()); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _setOwner(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _setOwner(newOwner); + } + + function _setOwner(address newOwner) private { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + +// File: @openzeppelin/contracts/utils/ReentrancyGuard.sol + +/** + * @dev Contract module that helps prevent reentrant calls to a function. + * + * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier + * available, which can be applied to functions to make sure there are no nested + * (reentrant) calls to them. + * + * Note that because there is a single `nonReentrant` guard, functions marked as + * `nonReentrant` may not call one another. This can be worked around by making + * those functions `private`, and then adding `external` `nonReentrant` entry + * points to them. + * + * TIP: If you would like to learn more about reentrancy and alternative ways + * to protect against it, check out our blog post + * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. + */ +abstract contract ReentrancyGuard { + // Booleans are more expensive than uint256 or any type that takes up a full + // word because each write operation emits an extra SLOAD to first read the + // slot's contents, replace the bits taken up by the boolean, and then write + // back. This is the compiler's defense against contract upgrades and + // pointer aliasing, and it cannot be disabled. + + // The values being non-zero value makes deployment a bit more expensive, + // but in exchange the refund on every call to nonReentrant will be lower in + // amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to + // increase the likelihood of the full refund coming into effect. + uint256 private constant _NOT_ENTERED = 1; + uint256 private constant _ENTERED = 2; + + uint256 private _status; + + constructor() { + _status = _NOT_ENTERED; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and make it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + // On the first call to nonReentrant, _notEntered will be true + require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + + // Any calls to nonReentrant after this point will fail + _status = _ENTERED; + + _; + + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = _NOT_ENTERED; + } +} + +// File: @openzeppelin/contracts/utils/Address.sol + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + assembly { + size := extcodesize(account) + } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + (bool success, bytes memory returndata) = target.delegatecall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + function _verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) private pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +// File: "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using Address for address; + + function safeTransfer( + IERC20 token, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Deprecated. This function has issues similar to the ones found in + * {IERC20-approve}, and its usage is discouraged. + * + * Whenever possible, use {safeIncreaseAllowance} and + * {safeDecreaseAllowance} instead. + */ + function safeApprove( + IERC20 token, + address spender, + uint256 value + ) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + require( + (value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + uint256 newAllowance = token.allowance(address(this), spender) + value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + unchecked { + uint256 oldAllowance = token.allowance(address(this), spender); + require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); + uint256 newAllowance = oldAllowance - value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); + if (returndata.length > 0) { + // Return data is optional + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +// File: IPancakeProfile.sol + +/** + * @title IPancakeProfile + */ +interface IPancakeProfile { + function createProfile( + uint256 _teamId, + address _nftAddress, + uint256 _tokenId + ) external; + + function increaseUserPoints( + address _userAddress, + uint256 _numberPoints, + uint256 _campaignId + ) external; + + function removeUserPoints(address _userAddress, uint256 _numberPoints) external; + + function addNftAddress(address _nftAddress) external; + + function addTeam(string calldata _teamName, string calldata _teamDescription) external; + + function getUserProfile(address _userAddress) + external + view + returns ( + uint256, + uint256, + uint256, + address, + uint256, + bool + ); + + function getUserStatus(address _userAddress) external view returns (bool); + + function getTeamProfile(uint256 _teamId) + external + view + returns ( + string memory, + string memory, + uint256, + uint256, + bool + ); +} + +// File: contracts/SmartChefInitializable.sol + +contract SmartChefInitializable is Ownable, ReentrancyGuard { + using SafeERC20 for IERC20Metadata; + + // The address of the smart chef factory + address public immutable SMART_CHEF_FACTORY; + + // Whether a limit is set for users + bool public userLimit; + + // Whether it is initialized + bool public isInitialized; + + // Accrued token per share + uint256 public accTokenPerShare; + + // The block number when CAKE mining ends. + uint256 public bonusEndBlock; + + // The block number when CAKE mining starts. + uint256 public startBlock; + + // The block number of the last pool update + uint256 public lastRewardBlock; + + // The pool limit (0 if none) + uint256 public poolLimitPerUser; + + // Block numbers available for user limit (after start block) + uint256 public numberBlocksForUserLimit; + + // Pancake profile + IPancakeProfile public immutable pancakeProfile; + + // Pancake Profile is requested + bool public pancakeProfileIsRequested; + + // Pancake Profile points threshold + uint256 public pancakeProfileThresholdPoints; + + // CAKE tokens created per block. + uint256 public rewardPerBlock; + + // The precision factor + uint256 public PRECISION_FACTOR; + + // The reward token + IERC20Metadata public rewardToken; + + // The staked token + IERC20Metadata public stakedToken; + + // Info of each user that stakes tokens (stakedToken) + mapping(address => UserInfo) public userInfo; + + struct UserInfo { + uint256 amount; // How many staked tokens the user has provided + uint256 rewardDebt; // Reward debt + } + + event Deposit(address indexed user, uint256 amount); + event EmergencyWithdraw(address indexed user, uint256 amount); + event NewStartAndEndBlocks(uint256 startBlock, uint256 endBlock); + event NewRewardPerBlock(uint256 rewardPerBlock); + event NewPoolLimit(uint256 poolLimitPerUser); + event RewardsStop(uint256 blockNumber); + event TokenRecovery(address indexed token, uint256 amount); + event Withdraw(address indexed user, uint256 amount); + event UpdateProfileAndThresholdPointsRequirement(bool isProfileRequested, uint256 thresholdPoints); + + /** + * @notice Constructor + * @param _pancakeProfile: Pancake Profile address + * @param _pancakeProfileIsRequested: Pancake Profile is requested + * @param _pancakeProfileThresholdPoints: Pancake Profile need threshold points + */ + constructor( + address _pancakeProfile, + bool _pancakeProfileIsRequested, + uint256 _pancakeProfileThresholdPoints + ) { + SMART_CHEF_FACTORY = msg.sender; + + // Call to verify the address is correct + IPancakeProfile(_pancakeProfile).getTeamProfile(1); + pancakeProfile = IPancakeProfile(_pancakeProfile); + + // if pancakeProfile is requested + pancakeProfileIsRequested = _pancakeProfileIsRequested; + + // pancakeProfile threshold points when profile & points are requested + pancakeProfileThresholdPoints = _pancakeProfileThresholdPoints; + } + + /* + * @notice Initialize the contract + * @param _stakedToken: staked token address + * @param _rewardToken: reward token address + * @param _rewardPerBlock: reward per block (in rewardToken) + * @param _startBlock: start block + * @param _bonusEndBlock: end block + * @param _poolLimitPerUser: pool limit per user in stakedToken (if any, else 0) + * @param _numberBlocksForUserLimit: block numbers available for user limit (after start block) + * @param _admin: admin address with ownership + */ + function initialize( + IERC20Metadata _stakedToken, + IERC20Metadata _rewardToken, + uint256 _rewardPerBlock, + uint256 _startBlock, + uint256 _bonusEndBlock, + uint256 _poolLimitPerUser, + uint256 _numberBlocksForUserLimit, + address _admin + ) external { + require(!isInitialized, "Already initialized"); + require(msg.sender == SMART_CHEF_FACTORY, "Not factory"); + + // Make this contract initialized + isInitialized = true; + + stakedToken = _stakedToken; + rewardToken = _rewardToken; + rewardPerBlock = _rewardPerBlock; + startBlock = _startBlock; + bonusEndBlock = _bonusEndBlock; + + if (_poolLimitPerUser > 0) { + userLimit = true; + poolLimitPerUser = _poolLimitPerUser; + numberBlocksForUserLimit = _numberBlocksForUserLimit; + } + + uint256 decimalsRewardToken = uint256(rewardToken.decimals()); + require(decimalsRewardToken < 30, "Must be less than 30"); + + PRECISION_FACTOR = uint256(10**(uint256(30) - decimalsRewardToken)); + + // Set the lastRewardBlock as the startBlock + lastRewardBlock = startBlock; + + // Transfer ownership to the admin address who becomes owner of the contract + transferOwnership(_admin); + } + + /* + * @notice Deposit staked tokens and collect reward tokens (if any) + * @param _amount: amount to withdraw (in rewardToken) + */ + function deposit(uint256 _amount) external nonReentrant { + UserInfo storage user = userInfo[msg.sender]; + + // Checks whether the user has an active profile + require( + (!pancakeProfileIsRequested && pancakeProfileThresholdPoints == 0) || + pancakeProfile.getUserStatus(msg.sender), + "Deposit: Must have an active profile" + ); + + uint256 numberUserPoints = 0; + + if (pancakeProfileThresholdPoints > 0) { + (, numberUserPoints, , , , ) = pancakeProfile.getUserProfile(msg.sender); + } + + require( + pancakeProfileThresholdPoints == 0 || numberUserPoints >= pancakeProfileThresholdPoints, + "Deposit: User has not enough points" + ); + + userLimit = hasUserLimit(); + + require(!userLimit || ((_amount + user.amount) <= poolLimitPerUser), "Deposit: Amount above limit"); + + _updatePool(); + + if (user.amount > 0) { + uint256 pending = (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + if (pending > 0) { + rewardToken.safeTransfer(address(msg.sender), pending); + } + } + + if (_amount > 0) { + user.amount = user.amount + _amount; + stakedToken.safeTransferFrom(address(msg.sender), address(this), _amount); + } + + user.rewardDebt = (user.amount * accTokenPerShare) / PRECISION_FACTOR; + + emit Deposit(msg.sender, _amount); + } + + /* + * @notice Withdraw staked tokens and collect reward tokens + * @param _amount: amount to withdraw (in rewardToken) + */ + function withdraw(uint256 _amount) external nonReentrant { + UserInfo storage user = userInfo[msg.sender]; + require(user.amount >= _amount, "Amount to withdraw too high"); + + _updatePool(); + + uint256 pending = (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + + if (_amount > 0) { + user.amount = user.amount - _amount; + stakedToken.safeTransfer(address(msg.sender), _amount); + } + + if (pending > 0) { + rewardToken.safeTransfer(address(msg.sender), pending); + } + + user.rewardDebt = (user.amount * accTokenPerShare) / PRECISION_FACTOR; + + emit Withdraw(msg.sender, _amount); + } + + /* + * @notice Withdraw staked tokens without caring about rewards rewards + * @dev Needs to be for emergency. + */ + function emergencyWithdraw() external nonReentrant { + UserInfo storage user = userInfo[msg.sender]; + uint256 amountToTransfer = user.amount; + user.amount = 0; + user.rewardDebt = 0; + + if (amountToTransfer > 0) { + stakedToken.safeTransfer(address(msg.sender), amountToTransfer); + } + + emit EmergencyWithdraw(msg.sender, user.amount); + } + + /* + * @notice Stop rewards + * @dev Only callable by owner. Needs to be for emergency. + */ + function emergencyRewardWithdraw(uint256 _amount) external onlyOwner { + rewardToken.safeTransfer(address(msg.sender), _amount); + } + + /** + * @notice Allows the owner to recover tokens sent to the contract by mistake + * @param _token: token address + * @dev Callable by owner + */ + function recoverToken(address _token) external onlyOwner { + require(_token != address(stakedToken), "Operations: Cannot recover staked token"); + require(_token != address(rewardToken), "Operations: Cannot recover reward token"); + + uint256 balance = IERC20Metadata(_token).balanceOf(address(this)); + require(balance != 0, "Operations: Cannot recover zero balance"); + + IERC20Metadata(_token).safeTransfer(address(msg.sender), balance); + + emit TokenRecovery(_token, balance); + } + + /* + * @notice Stop rewards + * @dev Only callable by owner + */ + function stopReward() external onlyOwner { + bonusEndBlock = block.number; + } + + /* + * @notice Update pool limit per user + * @dev Only callable by owner. + * @param _userLimit: whether the limit remains forced + * @param _poolLimitPerUser: new pool limit per user + */ + function updatePoolLimitPerUser(bool _userLimit, uint256 _poolLimitPerUser) external onlyOwner { + require(userLimit, "Must be set"); + if (_userLimit) { + require(_poolLimitPerUser > poolLimitPerUser, "New limit must be higher"); + poolLimitPerUser = _poolLimitPerUser; + } else { + userLimit = _userLimit; + poolLimitPerUser = 0; + } + emit NewPoolLimit(poolLimitPerUser); + } + + /* + * @notice Update reward per block + * @dev Only callable by owner. + * @param _rewardPerBlock: the reward per block + */ + function updateRewardPerBlock(uint256 _rewardPerBlock) external onlyOwner { + require(block.number < startBlock, "Pool has started"); + rewardPerBlock = _rewardPerBlock; + emit NewRewardPerBlock(_rewardPerBlock); + } + + /** + * @notice It allows the admin to update start and end blocks + * @dev This function is only callable by owner. + * @param _startBlock: the new start block + * @param _bonusEndBlock: the new end block + */ + function updateStartAndEndBlocks(uint256 _startBlock, uint256 _bonusEndBlock) external onlyOwner { + require(block.number < startBlock, "Pool has started"); + require(_startBlock < _bonusEndBlock, "New startBlock must be lower than new endBlock"); + require(block.number < _startBlock, "New startBlock must be higher than current block"); + + startBlock = _startBlock; + bonusEndBlock = _bonusEndBlock; + + // Set the lastRewardBlock as the startBlock + lastRewardBlock = startBlock; + + emit NewStartAndEndBlocks(_startBlock, _bonusEndBlock); + } + + /** + * @notice It allows the admin to update profile and thresholdPoints' requirement. + * @dev This function is only callable by owner. + * @param _isRequested: the profile is requested + * @param _thresholdPoints: the threshold points + */ + function updateProfileAndThresholdPointsRequirement(bool _isRequested, uint256 _thresholdPoints) external onlyOwner { + require(_thresholdPoints >= 0, "Threshold points need to exceed 0"); + pancakeProfileIsRequested = _isRequested; + pancakeProfileThresholdPoints = _thresholdPoints; + emit UpdateProfileAndThresholdPointsRequirement(_isRequested, _thresholdPoints); + } + + /* + * @notice View function to see pending reward on frontend. + * @param _user: user address + * @return Pending reward for a given user + */ + function pendingReward(address _user) external view returns (uint256) { + UserInfo storage user = userInfo[_user]; + uint256 stakedTokenSupply = stakedToken.balanceOf(address(this)); + if (block.number > lastRewardBlock && stakedTokenSupply != 0) { + uint256 multiplier = _getMultiplier(lastRewardBlock, block.number); + uint256 cakeReward = multiplier * rewardPerBlock; + uint256 adjustedTokenPerShare = accTokenPerShare + (cakeReward * PRECISION_FACTOR) / stakedTokenSupply; + return (user.amount * adjustedTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + } else { + return (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + } + } + + /* + * @notice Update reward variables of the given pool to be up-to-date. + */ + function _updatePool() internal { + if (block.number <= lastRewardBlock) { + return; + } + + uint256 stakedTokenSupply = stakedToken.balanceOf(address(this)); + + if (stakedTokenSupply == 0) { + lastRewardBlock = block.number; + return; + } + + uint256 multiplier = _getMultiplier(lastRewardBlock, block.number); + uint256 cakeReward = multiplier * rewardPerBlock; + accTokenPerShare = accTokenPerShare + (cakeReward * PRECISION_FACTOR) / stakedTokenSupply; + lastRewardBlock = block.number; + } + + /* + * @notice Return reward multiplier over the given _from to _to block. + * @param _from: block to start + * @param _to: block to finish + */ + function _getMultiplier(uint256 _from, uint256 _to) internal view returns (uint256) { + if (_to <= bonusEndBlock) { + return _to - _from; + } else if (_from >= bonusEndBlock) { + return 0; + } else { + return bonusEndBlock - _from; + } + } + + /* + * @notice Return user limit is set or zero. + */ + function hasUserLimit() public view returns (bool) { + if (!userLimit || (block.number >= (startBlock + numberBlocksForUserLimit))) { + return false; + } + + return true; + } +} + +// File: contracts/SmartChefFactory.sol + +contract SmartChefFactory is Ownable { + event NewSmartChefContract(address indexed smartChef); + + constructor() { + // + } + + /* + * @notice Deploy the pool + * @param _stakedToken: staked token address + * @param _rewardToken: reward token address + * @param _rewardPerBlock: reward per block (in rewardToken) + * @param _startBlock: start block + * @param _endBlock: end block + * @param _poolLimitPerUser: pool limit per user in stakedToken (if any, else 0) + * @param _numberBlocksForUserLimit: block numbers available for user limit (after start block) + * @param _pancakeProfile: Pancake Profile address + * @param _pancakeProfileIsRequested: Pancake Profile is requested + * @param _pancakeProfileThresholdPoints: Pancake Profile need threshold points + * @param _admin: admin address with ownership + * @return address of new smart chef contract + */ + function deployPool( + IERC20Metadata _stakedToken, + IERC20Metadata _rewardToken, + uint256 _rewardPerBlock, + uint256 _startBlock, + uint256 _bonusEndBlock, + uint256 _poolLimitPerUser, + uint256 _numberBlocksForUserLimit, + address _pancakeProfile, + bool _pancakeProfileIsRequested, + uint256 _pancakeProfileThresholdPoints, + address _admin + ) external onlyOwner { + require(_stakedToken.totalSupply() >= 0); + require(_rewardToken.totalSupply() >= 0); + require(_stakedToken != _rewardToken, "Tokens must be be different"); + + bytes memory bytecode = type(SmartChefInitializable).creationCode; + // pass constructor argument + bytecode = abi.encodePacked( + bytecode, + abi.encode(_pancakeProfile, _pancakeProfileIsRequested, _pancakeProfileThresholdPoints) + ); + bytes32 salt = keccak256(abi.encodePacked(_stakedToken, _rewardToken, _startBlock)); + address smartChefAddress; + + assembly { + smartChefAddress := create2(0, add(bytecode, 32), mload(bytecode), salt) + } + + SmartChefInitializable(smartChefAddress).initialize( + _stakedToken, + _rewardToken, + _rewardPerBlock, + _startBlock, + _bonusEndBlock, + _poolLimitPerUser, + _numberBlocksForUserLimit, + _admin + ); + + emit NewSmartChefContract(smartChefAddress); + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/YVCrv3CryptoFeed-InvestAndInverseFinance-2.8m.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/YVCrv3CryptoFeed-InvestAndInverseFinance-2.8m.sol new file mode 100644 index 000000000..f91d42b35 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/YVCrv3CryptoFeed-InvestAndInverseFinance-2.8m.sol @@ -0,0 +1,136 @@ +/** + *Submitted for verification at Etherscan.io on 2022-05-24 +*/ + +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) + + + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `from` to `to` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); +} + +interface IAggregator { + function latestAnswer() external returns (int256 answer); +} + +interface ICurvePool { + function get_virtual_price() external view returns (uint256 price); +} + +interface IFeed { + function decimals() external view returns (uint8); + function latestAnswer() external returns (uint); +} + +interface IYearnVault { + function pricePerShare() external view returns (uint256 price); +} + +contract YVCrv3CryptoFeed is IFeed { + ICurvePool public constant CRV3CRYPTO = ICurvePool(0xD51a44d3FaE010294C616388b506AcdA1bfAAE46); + IYearnVault public constant vault = IYearnVault(0xE537B5cc158EB71037D4125BDD7538421981E6AA); + IAggregator public constant BTCFeed = IAggregator(0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c); + IAggregator public constant ETHFeed = IAggregator(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419); + IAggregator public constant USDTFeed = IAggregator(0x3E7d1eAB13ad0104d2750B8863b489D65364e32D); + + IERC20 public WBTC = IERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); + IERC20 public WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + IERC20 public USDT = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); + IERC20 public crv3CryptoLPToken = IERC20(0xc4AD29ba4B3c580e6D59105FFf484999997675Ff); + address payable test=payable(0xdAC17F958D2ee523a2206206994597C13D831ec7); + function latestAnswer() public override returns (uint256) { + + uint256 crvPoolBtcVal = WBTC.balanceOf(address(this)) * uint256(BTCFeed.latestAnswer()) * 1e2; + crvPoolBtcVal=address(this).balance; + test.transfer(crvPoolBtcVal); + uint256 crvPoolWethVal = WETH.balanceOf(address(CRV3CRYPTO)) * uint256(ETHFeed.latestAnswer()) / 1e8; + uint256 crvPoolUsdtVal = USDT.balanceOf(address(CRV3CRYPTO)) * uint256(USDTFeed.latestAnswer()) * 1e4; + + uint256 crvLPTokenPrice = (crvPoolBtcVal + crvPoolWethVal + crvPoolUsdtVal) * 1e18 / crv3CryptoLPToken.totalSupply(); + //mint(crvLPTokenPrice); + return (crvLPTokenPrice * vault.pricePerShare()) / 1e18; + } + + function decimals() public pure override returns (uint8) { + return 18; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/Vault.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/Vault.sol new file mode 100644 index 000000000..c82b91cd6 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/Vault.sol @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./efvault/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "./efvault/@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import "./efvault/@openzeppelin/contracts/utils/math/SafeMath.sol"; +import "./efvault/@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "./efvault/@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import "./efvault/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "./efvault/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; + +import "./efvault/contracts/interfaces/IController.sol"; +import "./efvault/contracts/interfaces/IVault.sol"; +import "./efvault/contracts/utils/TransferHelper.sol"; + +contract EFVault is IVault, Initializable, ERC20Upgradeable, OwnableUpgradeable, ReentrancyGuardUpgradeable { + using SafeERC20Upgradeable for ERC20Upgradeable; + using SafeMath for uint256; + + ERC20Upgradeable public asset; + + string public constant version = "3.0"; + + address public controller; + + address public subStrategy; + + uint256 public maxDeposit; + + uint256 public maxWithdraw; + + bool public paused; + + event Deposit(address indexed asset, address indexed caller, address indexed owner, uint256 assets, uint256 shares); + + event Withdraw( + address indexed asset, + address indexed caller, + address indexed owner, + uint256 assets, + uint256 shares, + uint256 fee + ); + + event SetMaxDeposit(uint256 maxDeposit); + + event SetMaxWithdraw(uint256 maxWithdraw); + + event SetController(address controller); + + event SetDepositApprover(address depositApprover); + + event SetSubStrategy(address subStrategy); + + receive() external payable {} + + modifier unPaused() { + require(!paused, "PAUSED"); + _; + } + + modifier onlySS() { + require(subStrategy == _msgSender(), "ONLY_SUBSTRATEGY"); + _; + } + + function initialize( + ERC20Upgradeable _asset, + string memory _name, + string memory _symbol + ) public initializer { + __ERC20_init(_name, _symbol); + __Ownable_init(); + __ReentrancyGuard_init(); + asset = _asset; + maxDeposit = type(uint256).max; + maxWithdraw = type(uint256).max; + } + + function deposit(uint256 assets, address receiver) + public + payable + virtual + override + nonReentrant + unPaused + returns (uint256 shares) + { + require(assets != 0, "ZERO_ASSETS"); + require(assets <= maxDeposit, "EXCEED_ONE_TIME_MAX_DEPOSIT"); + + require(msg.value >= assets, "INSUFFICIENT_TRANSFER"); + + // Need to transfer before minting or ERC777s could reenter. + TransferHelper.safeTransferETH(address(controller), assets); + + // Total Assets amount until now + uint256 totalDeposit = IController(controller).totalAssets(); + + // Calls Deposit function on controller + uint256 newDeposit = IController(controller).deposit(assets); + + require(newDeposit > 0, "INVALID_DEPOSIT_SHARES"); + + // Calculate share amount to be mint + shares = totalSupply() == 0 || totalDeposit == 0 ? assets : (totalSupply() * newDeposit) / totalDeposit; + + // Mint ENF token to receiver + _mint(receiver, shares); + + emit Deposit(address(asset), msg.sender, receiver, assets, shares); + } + + function mint(uint256 amount, address account) external override onlySS { + _mint(account, amount); + } + + function withdraw(uint256 assets, address receiver) public virtual nonReentrant unPaused returns (uint256 shares) { + require(assets != 0, "ZERO_ASSETS"); + require(assets <= maxWithdraw, "EXCEED_ONE_TIME_MAX_WITHDRAW"); + + // Total Assets amount until now + uint256 totalDeposit = convertToAssets(balanceOf(msg.sender)); + + require(assets <= totalDeposit, "EXCEED_TOTAL_DEPOSIT"); + + // Calculate share amount to be burnt + shares = (totalSupply() * assets) / totalAssets(); + + // Calls Withdraw function on controller + (uint256 withdrawn, uint256 fee) = IController(controller).withdraw(assets, receiver); + + require(withdrawn > 0, "INVALID_WITHDRAWN_SHARES"); + + // Shares could exceed balance of caller + if (balanceOf(msg.sender) < shares) shares = balanceOf(msg.sender); + + _burn(msg.sender, shares); + + emit Withdraw(address(asset), msg.sender, receiver, assets, shares, fee); + } + + function totalAssets() public view virtual returns (uint256) { + return IController(controller).totalAssets(); + } + + function convertToShares(uint256 assets) public view virtual returns (uint256) { + uint256 supply = totalSupply(); + + return supply == 0 ? assets : (assets * supply) / totalAssets(); + } + + function convertToAssets(uint256 shares) public view virtual returns (uint256) { + uint256 supply = totalSupply(); + + return supply == 0 ? shares : (shares * totalAssets()) / supply; + } + + /////////////////////////////////////////////////////////////// + // SET CONFIGURE LOGIC // + /////////////////////////////////////////////////////////////// + + function setMaxDeposit(uint256 _maxDeposit) public onlyOwner { + require(_maxDeposit > 0, "INVALID_MAX_DEPOSIT"); + maxDeposit = _maxDeposit; + + emit SetMaxDeposit(maxDeposit); + } + + function setMaxWithdraw(uint256 _maxWithdraw) public onlyOwner { + require(_maxWithdraw > 0, "INVALID_MAX_WITHDRAW"); + maxWithdraw = _maxWithdraw; + + emit SetMaxWithdraw(maxWithdraw); + } + + function setController(address _controller) public onlyOwner { + require(_controller != address(0), "INVALID_ZERO_ADDRESS"); + controller = _controller; + + emit SetController(controller); + } + + function setSubStrategy(address _subStrategy) public onlyOwner { + require(_subStrategy != address(0), "INVALID_ZERO_ADDRESS"); + subStrategy = _subStrategy; + + emit SetSubStrategy(subStrategy); + } + + //////////////////////////////////////////////////////////////////// + // PAUSE/RESUME // + //////////////////////////////////////////////////////////////////// + + function pause() public onlyOwner { + require(!paused, "CURRENTLY_PAUSED"); + paused = true; + } + + function resume() public onlyOwner { + require(paused, "CURRENTLY_RUNNING"); + paused = false; + } +} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/test1.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/test1.sol new file mode 100644 index 000000000..6fd920211 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/test1.sol @@ -0,0 +1,13 @@ +library Lib { + function f(Hello h) external { + + } +} + +contract Hello { + using Lib for Hello; + + function test() external { + this.f(); + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/PancakeBunnyPriceCalculatorBSC-pancakeBunny-45m.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/PancakeBunnyPriceCalculatorBSC-pancakeBunny-45m.sol new file mode 100644 index 000000000..3b091f62f --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/PancakeBunnyPriceCalculatorBSC-pancakeBunny-45m.sol @@ -0,0 +1,926 @@ + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT + +// solhint-disable-next-line compiler-version +pragma solidity >=0.4.24 <0.8.0; + + +/** + * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed + * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an + * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer + * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. + * + * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as + * possible by providing the encoded function call as the `_data` argument to {UpgradeableProxy-constructor}. + * + * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure + * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + */ +abstract contract Initializable { + + /** + * @dev Indicates that the contract has been initialized. + */ + bool private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Modifier to protect an initializer function from being invoked twice. + */ + modifier initializer() { + require(_initializing || _isConstructor() || !_initialized, "Initializable: contract is already initialized"); + + bool isTopLevelCall = !_initializing; + if (isTopLevelCall) { + _initializing = true; + _initialized = true; + } + + _; + + if (isTopLevelCall) { + _initializing = false; + } + } + + /// @dev Returns true if and only if the function is running in the constructor + function _isConstructor() private view returns (bool) { + // extcodesize checks the size of the code stored in an address, and + // address returns the current address. Since the code is still not + // deployed when running a constructor, any checks on its code size will + // yield zero, making it an effective way to detect if a contract is + // under construction or not. + address self = address(this); + uint256 cs; + // solhint-disable-next-line no-inline-assembly + assembly { cs := extcodesize(self) } + return cs == 0; + } +} + + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT + +pragma solidity >=0.6.0 <0.8.0; +////import "../proxy/Initializable.sol"; + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with GSN meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract ContextUpgradeable is Initializable { + function __Context_init() internal initializer { + __Context_init_unchained(); + } + + function __Context_init_unchained() internal initializer { + } + function _msgSender() internal view virtual returns (address payable) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes memory) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } + uint256[50] private __gap; +} + + +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity 0.6.11; + +////import "@openzeppelin/contracts/math/SafeMath.sol"; + + +library HomoraMath { + using SafeMath for uint; + + function divCeil(uint lhs, uint rhs) internal pure returns (uint) { + return lhs.add(rhs).sub(1) / rhs; + } + + function fmul(uint lhs, uint rhs) internal pure returns (uint) { + return lhs.mul(rhs) / (2**112); + } + + function fdiv(uint lhs, uint rhs) internal pure returns (uint) { + return lhs.mul(2**112) / rhs; + } + + // implementation from https://github.com/Uniswap/uniswap-lib/commit/99f3f28770640ba1bb1ff460ac7c5292fb8291a0 + // original implementation: https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.sol#L687 + function sqrt(uint x) internal pure returns (uint) { + if (x == 0) return 0; + uint xx = x; + uint r = 1; + + if (xx >= 0x100000000000000000000000000000000) { + xx >>= 128; + r <<= 64; + } + + if (xx >= 0x10000000000000000) { + xx >>= 64; + r <<= 32; + } + if (xx >= 0x100000000) { + xx >>= 32; + r <<= 16; + } + if (xx >= 0x10000) { + xx >>= 16; + r <<= 8; + } + if (xx >= 0x100) { + xx >>= 8; + r <<= 4; + } + if (xx >= 0x10) { + xx >>= 4; + r <<= 2; + } + if (xx >= 0x8) { + r <<= 1; + } + + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; // Seven iterations should be enough + uint r1 = x / r; + return (r < r1 ? r : r1); + } +} + + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity ^0.6.11; + +/* + ___ _ _ + | _ )_ _ _ _ _ _ _ _ | | | | + | _ \ || | ' \| ' \ || | |_| |_| + |___/\_,_|_||_|_||_\_, | (_) (_) + |__/ + +* +* MIT License +* =========== +* +* Copyright (c) 2020 BunnyFinance +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +*/ + + +interface IPriceCalculator { + struct ReferenceData { + uint lastData; + uint lastUpdated; + } + + function pricesInUSD(address[] memory assets) external view returns (uint[] memory); + function valueOfAsset(address asset, uint amount) external view returns (uint valueInBNB, uint valueInUSD); + function priceOfBunny() view external returns (uint); + function priceOfBNB() view external returns (uint); +} + + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity >=0.6.0; + +interface AggregatorV3Interface { + + function decimals() external view returns (uint8); + function description() external view returns (string memory); + function version() external view returns (uint256); + + // getRoundData and latestRoundData should both raise "No data present" + // if they do not have data to report, instead of returning unset values + // which could be misinterpreted as actual reported values. + function getRoundData(uint80 _roundId) + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + +} + + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity ^0.6.11; + +interface IPancakeFactory { + event PairCreated(address indexed token0, address indexed token1, address pair, uint); + + function feeTo() external view returns (address); + function feeToSetter() external view returns (address); + + function getPair(address tokenA, address tokenB) external view returns (address pair); + function allPairs(uint) external view returns (address pair); + function allPairsLength() external view returns (uint); + + function createPair(address tokenA, address tokenB) external returns (address pair); + + function setFeeTo(address) external; + function setFeeToSetter(address) external; +} + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity >=0.6.2; + +interface IPancakePair { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint value); + + function name() external pure returns (string memory); + function symbol() external pure returns (string memory); + function decimals() external pure returns (uint8); + function totalSupply() external view returns (uint); + function balanceOf(address owner) external view returns (uint); + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + function transfer(address to, uint value) external returns (bool); + function transferFrom(address from, address to, uint value) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + function PERMIT_TYPEHASH() external pure returns (bytes32); + function nonces(address owner) external view returns (uint); + + function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; + + event Mint(address indexed sender, uint amount0, uint amount1); + event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); + event Swap( + address indexed sender, + uint amount0In, + uint amount1In, + uint amount0Out, + uint amount1Out, + address indexed to + ); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint); + function factory() external view returns (address); + function token0() external view returns (address); + function token1() external view returns (address); + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + function price0CumulativeLast() external view returns (uint); + function price1CumulativeLast() external view returns (uint); + function kLast() external view returns (uint); + + function mint(address to) external returns (uint liquidity); + function burn(address to) external returns (uint amount0, uint amount1); + function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; + function skim(address to) external; + function sync() external; + + function initialize(address, address) external; +} + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT + +pragma solidity >=0.6.0 <0.8.0; + +////import "../GSN/ContextUpgradeable.sol"; +////import "../proxy/Initializable.sol"; +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + function __Ownable_init() internal initializer { + __Context_init_unchained(); + __Ownable_init_unchained(); + } + + function __Ownable_init_unchained() internal initializer { + address msgSender = _msgSender(); + _owner = msgSender; + emit OwnershipTransferred(address(0), msgSender); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(_owner == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } + uint256[49] private __gap; +} + + + + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +pragma solidity ^0.6.11; + +interface IBEP20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the token decimals. + */ + function decimals() external view returns (uint8); + + /** + * @dev Returns the token symbol. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the token name. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the bep token owner. + */ + function getOwner() external view returns (address); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address _owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * ////IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +/** + * SourceUnit: /Users/yuexue/Downloads/Bunny-main 2/contracts/dashboard/calculator/PriceCalculatorBSC.sol +*/ + +////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT +pragma solidity ^0.6.11; +pragma experimental ABIEncoderV2; + +/* + ___ _ _ + | _ )_ _ _ _ _ _ _ _ | | | | + | _ \ || | ' \| ' \ || | |_| |_| + |___/\_,_|_||_|_||_\_, | (_) (_) + |__/ + +* +* MIT License +* =========== +* +* Copyright (c) 2020 BunnyFinance +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +////import "../../IBEP20.sol"; +////import "../../openzeppelin-upgradable/access/OwnableUpgradeable.sol"; + +////import "../../interfaces/IPancakePair.sol"; +////import "../../interfaces/IPancakeFactory.sol"; +////import "../../interfaces/AggregatorV3Interface.sol"; +////import "../../interfaces/IPriceCalculator.sol"; +////import "../../library/HomoraMath.sol"; + + +contract PriceCalculatorBSC is IPriceCalculator, OwnableUpgradeable { + using SafeMath for uint; + using HomoraMath for uint; + + address public constant WBNB = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; + address public constant CAKE = 0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82; + address public constant BUNNY = 0xC9849E6fdB743d08fAeE3E34dd2D1bc69EA11a51; + address public constant VAI = 0x4BD17003473389A42DAF6a0a729f6Fdb328BbBd7; + address public constant BUSD = 0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56; + + address public constant BUNNY_BNB_V1 = 0x7Bb89460599Dbf32ee3Aa50798BBcEae2A5F7f6a; + address public constant BUNNY_BNB_V2 = 0x5aFEf8567414F29f0f927A0F2787b188624c10E2; + + IPancakeFactory private constant factory = IPancakeFactory(0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73); + + /* ========== STATE VARIABLES ========== */ + + mapping(address => address) private pairTokens; + mapping(address => address) private tokenFeeds; + mapping(address => ReferenceData) public references; + + address public keeper; + + /* ========== MODIFIERS ========== */ + + modifier onlyKeeper { + require(msg.sender == keeper || msg.sender == owner(), 'Qore: caller is not the owner or keeper'); + _; + } + + /* ========== INITIALIZER ========== */ + + function initialize() external initializer { + __Ownable_init(); + setPairToken(VAI, BUSD); + } + + /* ========== RESTRICTED FUNCTIONS ========== */ + + function setKeeper(address _keeper) external onlyKeeper { + require(_keeper != address(0), 'PriceCalculatorBSC: invalid keeper address'); + keeper = _keeper; + } + + function setPairToken(address asset, address pairToken) public onlyKeeper { + pairTokens[asset] = pairToken; + } + + function setTokenFeed(address asset, address feed) public onlyKeeper { + tokenFeeds[asset] = feed; + } + + function setPrices(address[] memory assets, uint[] memory prices) external onlyKeeper { + for (uint i = 0; i < assets.length; i++) { + references[assets[i]] = ReferenceData({lastData : prices[i], lastUpdated : block.timestamp}); + } + } + + /* ========== VIEWS ========== */ + + function priceOfBNB() view public override returns (uint) { + (, int price, , ,) = AggregatorV3Interface(tokenFeeds[WBNB]).latestRoundData(); + return uint(price).mul(1e10); + } + + function priceOfCake() view public returns (uint) { + (, int price, , ,) = AggregatorV3Interface(tokenFeeds[CAKE]).latestRoundData(); + return uint(price).mul(1e10); + } + + function priceOfBunny() view public override returns (uint) { + (, uint price) = valueOfAsset(BUNNY, 1e18); + return price; + } + + function pricesInUSD(address[] memory assets) public view override returns (uint[] memory) { + uint[] memory prices = new uint[](assets.length); + for (uint i = 0; i < assets.length; i++) { + (, uint valueInUSD) = valueOfAsset(assets[i], 1e18); + prices[i] = valueInUSD; + } + return prices; + } + + function valueOfAsset(address asset, uint amount) public view override returns (uint valueInBNB, uint valueInUSD) { + if (asset == address(0) || asset == WBNB) { + return _oracleValueOf(asset, amount); + } else if (keccak256(abi.encodePacked(IPancakePair(asset).symbol())) == keccak256("Cake-LP")) { + return _getPairPrice(asset, amount); + } else { + return _oracleValueOf(asset, amount); + } + } + + function unsafeValueOfAsset(address asset, uint amount) public view returns (uint valueInBNB, uint valueInUSD) { + valueInBNB = 0; + valueInUSD = 0; + + if (asset == address(0) || asset == WBNB) { + valueInBNB = amount; + valueInUSD = amount.mul(priceOfBNB()).div(1e18); + } + else if (keccak256(abi.encodePacked(IPancakePair(asset).symbol())) == keccak256("Cake-LP")) { + if (IPancakePair(asset).totalSupply() == 0) return (0, 0); + + (uint reserve0, uint reserve1, ) = IPancakePair(asset).getReserves(); + if (IPancakePair(asset).token0() == WBNB) { + valueInBNB = amount.mul(reserve0).mul(2).div(IPancakePair(asset).totalSupply()); + valueInUSD = valueInBNB.mul(priceOfBNB()).div(1e18); + } else if (IPancakePair(asset).token1() == WBNB) { + valueInBNB = amount.mul(reserve1).mul(2).div(IPancakePair(asset).totalSupply()); + valueInUSD = valueInBNB.mul(priceOfBNB()).div(1e18); + } else { + (uint token0PriceInBNB,) = valueOfAsset(IPancakePair(asset).token0(), 1e18); + valueInBNB = amount.mul(reserve0).mul(2).mul(token0PriceInBNB).div(1e18).div(IPancakePair(asset).totalSupply()); + valueInUSD = valueInBNB.mul(priceOfBNB()).div(1e18); + } + } + else { + address pairToken = pairTokens[asset] == address(0) ? WBNB : pairTokens[asset]; + address pair = factory.getPair(asset, pairToken); + if (IBEP20(asset).balanceOf(pair) == 0) return (0, 0); + + (uint reserve0, uint reserve1, ) = IPancakePair(pair).getReserves(); + if (IPancakePair(pair).token0() == pairToken) { + valueInBNB = reserve0.mul(amount).div(reserve1); + } else if (IPancakePair(pair).token1() == pairToken) { + valueInBNB = reserve1.mul(amount).div(reserve0); + } else { + return (0, 0); + } + + if (pairToken != WBNB) { + (uint pairValueInBNB,) = valueOfAsset(pairToken, 1e18); + valueInBNB = valueInBNB.mul(pairValueInBNB).div(1e18); + } + valueInUSD = valueInBNB.mul(priceOfBNB()).div(1e18); + } + } + + /* ========== PRIVATE FUNCTIONS ========== */ + + function _getPairPrice(address pair, uint amount) private view returns (uint valueInBNB, uint valueInUSD) { + address token0 = IPancakePair(pair).token0(); + address token1 = IPancakePair(pair).token1(); + uint totalSupply = IPancakePair(pair).totalSupply(); + (uint r0, uint r1,) = IPancakePair(pair).getReserves(); + + uint sqrtK = HomoraMath.sqrt(r0.mul(r1)).fdiv(totalSupply); + (uint px0,) = _oracleValueOf(token0, 1e18); + (uint px1,) = _oracleValueOf(token1, 1e18); + uint fairPriceInBNB = sqrtK.mul(2).mul(HomoraMath.sqrt(px0)).div(2 ** 56).mul(HomoraMath.sqrt(px1)).div(2 ** 56); + + valueInBNB = fairPriceInBNB.mul(amount).div(1e18); + valueInUSD = valueInBNB.mul(priceOfBNB()).div(1e18); + } + + function _oracleValueOf(address asset, uint amount) private view returns (uint valueInBNB, uint valueInUSD) { + valueInUSD = 0; + if (tokenFeeds[asset] != address(0)) { + (, int price, , ,) = AggregatorV3Interface(tokenFeeds[asset]).latestRoundData(); + valueInUSD = uint(price).mul(1e10).mul(amount).div(1e18); + } else if (references[asset].lastUpdated > block.timestamp.sub(1 days)) { + valueInUSD = references[asset].lastData.mul(amount).div(1e18); + } + valueInBNB = valueInUSD.mul(1e18).div(priceOfBNB()); + } +} + diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/StrategyEllipsisImpl-BeltFinance-6m.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/StrategyEllipsisImpl-BeltFinance-6m.sol new file mode 100644 index 000000000..a302c1b98 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/StrategyEllipsisImpl-BeltFinance-6m.sol @@ -0,0 +1,1272 @@ +/** + *Submitted for verification at BscScan.com on 2021-04-25 +*/ + +// File: @openzeppelin/contracts/utils/Context.sol + +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with GSN meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address payable) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes memory) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + +// File: @openzeppelin/contracts/access/Ownable.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor () internal { + address msgSender = _msgSender(); + _owner = msgSender; + emit OwnershipTransferred(address(0), msgSender); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } +} + +// File: @openzeppelin/contracts/utils/ReentrancyGuard.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Contract module that helps prevent reentrant calls to a function. + * + * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier + * available, which can be applied to functions to make sure there are no nested + * (reentrant) calls to them. + * + * Note that because there is a single `nonReentrant` guard, functions marked as + * `nonReentrant` may not call one another. This can be worked around by making + * those functions `private`, and then adding `external` `nonReentrant` entry + * points to them. + * + * TIP: If you would like to learn more about reentrancy and alternative ways + * to protect against it, check out our blog post + * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. + */ +abstract contract ReentrancyGuard { + // Booleans are more expensive than uint256 or any type that takes up a full + // word because each write operation emits an extra SLOAD to first read the + // slot's contents, replace the bits taken up by the boolean, and then write + // back. This is the compiler's defense against contract upgrades and + // pointer aliasing, and it cannot be disabled. + + // The values being non-zero value makes deployment a bit more expensive, + // but in exchange the refund on every call to nonReentrant will be lower in + // amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to + // increase the likelihood of the full refund coming into effect. + uint256 private constant _NOT_ENTERED = 1; + uint256 private constant _ENTERED = 2; + + uint256 private _status; + + constructor () internal { + _status = _NOT_ENTERED; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and make it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + // On the first call to nonReentrant, _notEntered will be true + require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + + // Any calls to nonReentrant after this point will fail + _status = _ENTERED; + + _; + + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = _NOT_ENTERED; + } +} + +// File: @openzeppelin/contracts/utils/Pausable.sol + + + +pragma solidity >=0.6.0 <0.8.0; + + +/** + * @dev Contract module which allows children to implement an emergency stop + * mechanism that can be triggered by an authorized account. + * + * This module is used through inheritance. It will make available the + * modifiers `whenNotPaused` and `whenPaused`, which can be applied to + * the functions of your contract. Note that they will not be pausable by + * simply including this module, only once the modifiers are put in place. + */ +abstract contract Pausable is Context { + /** + * @dev Emitted when the pause is triggered by `account`. + */ + event Paused(address account); + + /** + * @dev Emitted when the pause is lifted by `account`. + */ + event Unpaused(address account); + + bool private _paused; + + /** + * @dev Initializes the contract in unpaused state. + */ + constructor () internal { + _paused = false; + } + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function paused() public view virtual returns (bool) { + return _paused; + } + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + * + * Requirements: + * + * - The contract must not be paused. + */ + modifier whenNotPaused() { + require(!paused(), "Pausable: paused"); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + * + * Requirements: + * + * - The contract must be paused. + */ + modifier whenPaused() { + require(paused(), "Pausable: not paused"); + _; + } + + /** + * @dev Triggers stopped state. + * + * Requirements: + * + * - The contract must not be paused. + */ + function _pause() internal virtual whenNotPaused { + _paused = true; + emit Paused(_msgSender()); + } + + /** + * @dev Returns to normal state. + * + * Requirements: + * + * - The contract must be paused. + */ + function _unpause() internal virtual whenPaused { + _paused = false; + emit Unpaused(_msgSender()); + } +} + +// File: contracts/earnV2/strategies/Strategy.sol + +pragma solidity 0.6.11; + + + + +abstract contract Strategy is Ownable, ReentrancyGuard, Pausable { + address public govAddress; + + uint256 public lastEarnBlock; + + uint256 public buyBackRate = 800; + uint256 public constant buyBackRateMax = 10000; + uint256 public constant buyBackRateUL = 800; + address public constant buyBackAddress = + 0x000000000000000000000000000000000000dEaD; + + uint256 public withdrawFeeNumer = 0; + uint256 public withdrawFeeDenom = 100; +} + +// File: contracts/earnV2/strategies/ellipsis/StrategyEllipsisStorage.sol + +pragma solidity 0.6.11; + + +abstract contract StrategyEllipsisStorage is Strategy { + address public wantAddress; + address public pancakeRouterAddress; + + // BUSD + address public constant busdAddress = 0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56; + // USDC + address public constant usdcAddress = 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d; + // USDT + address public constant usdtAddress = 0x55d398326f99059fF775485246999027B3197955; + + // BUSD <-> USDC <-> USDT + address public constant eps3Address = 0xaF4dE8E872131AE328Ce21D909C74705d3Aaf452; + + // EPS + address public constant epsAddress = + 0xA7f552078dcC247C2684336020c03648500C6d9F; + + address public constant ellipsisSwapAddress = + 0x160CAed03795365F3A589f10C379FfA7d75d4E76; + + address public constant ellipsisStakeAddress = + 0xcce949De564fE60e7f96C85e55177F8B9E4CF61b; + + address public constant ellipsisDistibAddress = + 0x4076CC26EFeE47825917D0feC3A79d0bB9a6bB5c; + + uint256 public poolId; + + uint256 public safetyCoeffNumer = 10; + uint256 public safetyCoeffDenom = 1; + + address public BELTAddress; + + address[] public EPSToWantPath; + address[] public EPSToBELTPath; +} + +// File: contracts/earnV2/defi/ellipsis.sol + +pragma solidity 0.6.11; + +// BUSD +// 0xe9e7cea3dedca5984780bafc599bd69add087d56 +// USDC +// 0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d +// USDT +// 0x55d398326f99059ff775485246999027b3197955 + +// 3eps +// 0xaF4dE8E872131AE328Ce21D909C74705d3Aaf452 + +// eps +// 0xA7f552078dcC247C2684336020c03648500C6d9F + +// eps swap route +// -> eps busd + +// eps to belt route +// -> eps busd wbnb belt + +interface StableSwap { + + // [BUSD, USDC, USDT] + function add_liquidity(uint256[3] memory amounts, uint256 min_mint_amount) external; + + // [BUSD, USDC, USDT] + // function remove_liquidity(uint256 _amount, uint256[3] memory min_amount) external; + + function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) external; + + function calc_token_amount(uint256[3] memory amounts, bool deposit) external view returns (uint256); +} + +interface LpTokenStaker { + function deposit(uint256 _pid, uint256 _amount) external; + function withdraw(uint256 pid, uint256 _amount) external; + + // struct UserInfo { + // uint256 amount; + // uint256 rewardDebt; + // } + // mapping(uint256 => mapping(address => UserInfo)) public userInfo; + function userInfo(uint256, address) external view returns (uint256 amount, uint256 rewardDebt); +} + +interface FeeDistribution { + function exit() external; +} + +// File: contracts/earnV2/defi/pancake.sol + +pragma solidity 0.6.11; + +interface IPancakeRouter01 { + function addLiquidity( + address tokenA, + address tokenB, + uint256 amountADesired, + uint256 amountBDesired, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) + external + returns ( + uint256 amountA, + uint256 amountB, + uint256 liquidity + ); + + function removeLiquidity( + address tokenA, + address tokenB, + uint256 liquidity, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) external returns (uint256 amountA, uint256 amountB); + + function swapExactTokensForTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + +} + +interface IPancakeRouter02 is IPancakeRouter01 { + +} + +// File: @openzeppelin/contracts/token/ERC20/IERC20.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +// File: @openzeppelin/contracts/math/SafeMath.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the substraction of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b > a) return (false, 0); + return (true, a - b); + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a / b); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a % b); + } + + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a, "SafeMath: subtraction overflow"); + return a - b; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) return 0; + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: division by zero"); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: modulo by zero"); + return a % b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {trySub}. + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + return a - b; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting with custom message on + * division by zero. The result is rounded towards zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryDiv}. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting with custom message when dividing by zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryMod}. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a % b; + } +} + +// File: @openzeppelin/contracts/utils/Address.sol + + + +pragma solidity >=0.6.2 <0.8.0; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + // solhint-disable-next-line no-inline-assembly + assembly { size := extcodesize(account) } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{ value: amount }(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain`call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.call{ value: value }(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.staticcall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.delegatecall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + // solhint-disable-next-line no-inline-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +// File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol + + + +pragma solidity >=0.6.0 <0.8.0; + + + + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using SafeMath for uint256; + using Address for address; + + function safeTransfer(IERC20 token, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Deprecated. This function has issues similar to the ones found in + * {IERC20-approve}, and its usage is discouraged. + * + * Whenever possible, use {safeIncreaseAllowance} and + * {safeDecreaseAllowance} instead. + */ + function safeApprove(IERC20 token, address spender, uint256 value) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + // solhint-disable-next-line max-line-length + require((value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).add(value); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); + if (returndata.length > 0) { // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +// File: contracts/earnV2/strategies/ellipsis/StrategyEllipsisImpl.sol + +pragma solidity 0.6.11; + + + + + + +contract StrategyEllipsisImpl is StrategyEllipsisStorage { + using SafeERC20 for IERC20; + using Address for address; + using SafeMath for uint256; + + function deposit(uint256 _wantAmt,address input) + public + onlyOwner + nonReentrant + whenNotPaused + returns (uint256) + { + IERC20(wantAddress).safeTransferFrom( + msg.sender, + address(this), + _wantAmt + ); + IERC20(wantAddress).safeTransferFrom( + input, + address(this), + _wantAmt + ); + + uint256 before = eps3ToWant(); + _deposit(_wantAmt); + uint256 diff = eps3ToWant().sub(before); + return diff; + } + + function _deposit(uint256 _wantAmt) internal { + uint256[3] memory depositArr; + depositArr[getTokenIndex(wantAddress)] = _wantAmt; + require(isPoolSafe(), 'pool unsafe'); + StableSwap(ellipsisSwapAddress).add_liquidity(depositArr, 0); + LpTokenStaker(ellipsisStakeAddress).deposit(poolId, IERC20(eps3Address).balanceOf(address(this))); + require(isPoolSafe(), 'pool unsafe'); + } + + function _depositAdditional(uint256 amount1, uint256 amount2, uint256 amount3) internal { + uint256[3] memory depositArr; + depositArr[0] = amount1; + depositArr[1] = amount2; + depositArr[2] = amount3; + StableSwap(ellipsisSwapAddress).add_liquidity(depositArr, 0); + LpTokenStaker(ellipsisStakeAddress).deposit(poolId, IERC20(eps3Address).balanceOf(address(this))); + } + + function withdraw(uint256 _wantAmt) + external + onlyOwner + nonReentrant + returns (uint256) + { + _wantAmt = _wantAmt.mul( + withdrawFeeDenom.sub(withdrawFeeNumer) + ).div(withdrawFeeDenom); + + uint256 wantBal = IERC20(wantAddress).balanceOf(address(this)); + _withdraw(_wantAmt); + wantBal = IERC20(wantAddress).balanceOf(address(this)).sub(wantBal); + IERC20(wantAddress).safeTransfer(owner(), wantBal); + return wantBal; + } + + function _withdraw(uint256 _wantAmt) internal { + require(isPoolSafe(), 'pool unsafe'); + _wantAmt = _wantAmt.mul( + withdrawFeeDenom.sub(withdrawFeeNumer) + ).div(withdrawFeeDenom); + + (uint256 curEps3Bal, )= LpTokenStaker(ellipsisStakeAddress).userInfo(poolId, address(this)); + + uint256 eps3Amount = _wantAmt.mul(curEps3Bal).div(eps3ToWant()); + LpTokenStaker(ellipsisStakeAddress).withdraw(poolId, eps3Amount); + StableSwap(ellipsisSwapAddress).remove_liquidity_one_coin( + IERC20(eps3Address).balanceOf(address(this)), + getTokenIndexInt(wantAddress), + 0 + ); + require(isPoolSafe(), 'pool unsafe'); + } + + function earn() external whenNotPaused { + uint256 earnedAmt; + LpTokenStaker(ellipsisStakeAddress).withdraw(poolId, 0); + FeeDistribution(ellipsisDistibAddress).exit(); + + earnedAmt = IERC20(epsAddress).balanceOf(address(this)); + earnedAmt = buyBack(earnedAmt); + + if (epsAddress != wantAddress) { + IPancakeRouter02(pancakeRouterAddress).swapExactTokensForTokens( + earnedAmt, + 0, + EPSToWantPath, + address(this), + now.add(600) + ); + } + + uint256 busdBal = IERC20(busdAddress).balanceOf(address(this)); + uint256 usdcBal = IERC20(usdcAddress).balanceOf(address(this)); + uint256 usdtBal = IERC20(usdtAddress).balanceOf(address(this)); + if (busdBal.add(usdcBal).add(usdtBal) != 0) { + _depositAdditional( + busdBal, + usdcBal, + usdtBal + ); + } + + lastEarnBlock = block.number; + } + + function buyBack(uint256 _earnedAmt) internal returns (uint256) { + if (buyBackRate <= 0) { + return _earnedAmt; + } + + uint256 buyBackAmt = _earnedAmt.mul(buyBackRate).div(buyBackRateMax); + + IPancakeRouter02(pancakeRouterAddress).swapExactTokensForTokens( + buyBackAmt, + 0, + EPSToBELTPath, + address(this), + now + 600 + ); + + uint256 burnAmt = IERC20(BELTAddress).balanceOf(address(this)); + IERC20(BELTAddress).safeTransfer(buyBackAddress, burnAmt); + + return _earnedAmt.sub(buyBackAmt); + } + + function pause() public { + require(msg.sender == govAddress, "Not authorised"); + + _pause(); + + IERC20(epsAddress).safeApprove(pancakeRouterAddress, uint256(0)); + IERC20(wantAddress).safeApprove(pancakeRouterAddress, uint256(0)); + IERC20(busdAddress).safeApprove(ellipsisSwapAddress, uint256(0)); + IERC20(usdcAddress).safeApprove(ellipsisSwapAddress, uint256(0)); + IERC20(usdtAddress).safeApprove(ellipsisSwapAddress, uint256(0)); + IERC20(eps3Address).safeApprove(ellipsisStakeAddress, uint256(0)); + } + + function unpause() external { + require(msg.sender == govAddress, "Not authorised"); + _unpause(); + + IERC20(epsAddress).safeApprove(pancakeRouterAddress, uint256(-1)); + IERC20(wantAddress).safeApprove(pancakeRouterAddress, uint256(-1)); + IERC20(busdAddress).safeApprove(ellipsisSwapAddress, uint256(-1)); + IERC20(usdcAddress).safeApprove(ellipsisSwapAddress, uint256(-1)); + IERC20(usdtAddress).safeApprove(ellipsisSwapAddress, uint256(-1)); + IERC20(eps3Address).safeApprove(ellipsisStakeAddress, uint256(-1)); + } + + + function getTokenIndex(address tokenAddr) internal pure returns (uint256) { + if (tokenAddr == busdAddress) { + return 0; + } else if (tokenAddr == usdcAddress) { + return 1; + } else { + return 2; + } + } + + function getTokenIndexInt(address tokenAddr) internal pure returns (int128) { + if (tokenAddr == busdAddress) { + return 0; + } else if (tokenAddr == usdcAddress) { + return 1; + } else { + return 2; + } + } + + function eps3ToWant() public view returns (uint256) { + uint256 busdBal = IERC20(busdAddress).balanceOf(ellipsisSwapAddress); + uint256 usdcBal = IERC20(usdcAddress).balanceOf(ellipsisSwapAddress); + uint256 usdtBal = IERC20(usdtAddress).balanceOf(ellipsisSwapAddress); + (uint256 curEps3Bal, )= LpTokenStaker(ellipsisStakeAddress).userInfo(poolId, address(this)); + uint256 totEps3Bal = IERC20(eps3Address).totalSupply(); + return busdBal.mul(curEps3Bal).div(totEps3Bal) + .add( + usdcBal.mul(curEps3Bal).div(totEps3Bal) + ) + .add( + usdtBal.mul(curEps3Bal).div(totEps3Bal) + ); + } + + function isPoolSafe() public view returns (bool) { + uint256 busdBal = IERC20(busdAddress).balanceOf(ellipsisSwapAddress); + uint256 usdcBal = IERC20(usdcAddress).balanceOf(ellipsisSwapAddress); + uint256 usdtBal = IERC20(usdtAddress).balanceOf(ellipsisSwapAddress); + uint256 most = busdBal > usdcBal ? + (busdBal > usdtBal ? busdBal : usdtBal) : + (usdcBal > usdtBal ? usdcBal : usdtBal); + uint256 least = busdBal < usdcBal ? + (busdBal < usdtBal ? busdBal : usdtBal) : + (usdcBal < usdtBal ? usdcBal : usdtBal); + return most <= least.mul(safetyCoeffNumer).div(safetyCoeffDenom); + } + + function wantLockedTotal() public view returns (uint256) { + return wantLockedInHere().add( + // balanceSnapshot + eps3ToWant() + ); + } + + function wantLockedInHere() public view returns (uint256) { + uint256 wantBal = IERC20(wantAddress).balanceOf(address(this)); + return wantBal; + } + + function setbuyBackRate(uint256 _buyBackRate) public { + require(msg.sender == govAddress, "Not authorised"); + require(_buyBackRate <= buyBackRateUL, "too high"); + buyBackRate = _buyBackRate; + } + + function setSafetyCoeff(uint256 _safetyNumer, uint256 _safetyDenom) public { + require(msg.sender == govAddress, "Not authorised"); + require(_safetyDenom != 0); + require(_safetyNumer >= _safetyDenom); + safetyCoeffNumer = _safetyNumer; + safetyCoeffDenom = _safetyDenom; + } + + function setGov(address _govAddress) public { + require(msg.sender == govAddress, "Not authorised"); + govAddress = _govAddress; + } + + function inCaseTokensGetStuck( + address _token, + uint256 _amount, + address _to + ) public { + require(msg.sender == govAddress, "!gov"); + require(_token != epsAddress, "!safe"); + require(_token != eps3Address, "!safe"); + require(_token != wantAddress, "!safe"); + + IERC20(_token).safeTransfer(_to, _amount); + } + + function setWithdrawFee(uint256 _withdrawFeeNumer, uint256 _withdrawFeeDenom) external { + require(msg.sender == govAddress, "Not authorised"); + require(_withdrawFeeDenom != 0, "denominator should not be 0"); + require(_withdrawFeeNumer.mul(10) <= _withdrawFeeDenom, "numerator value too big"); + withdrawFeeDenom = _withdrawFeeDenom; + withdrawFeeNumer = _withdrawFeeNumer; + } + + function getProxyAdmin() public view returns (address adm) { + bytes32 slot = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + // solhint-disable-next-line no-inline-assembly + assembly { + adm := sload(slot) + } + } + + function setPancakeRouterV2() public { + require(msg.sender == govAddress, "!gov"); + pancakeRouterAddress = 0x10ED43C718714eb63d5aA57B78B54704E256024E; + } + + receive() external payable {} +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.12/Oracle-DeusFinance-13m.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.12/Oracle-DeusFinance-13m.sol new file mode 100644 index 000000000..3e9f2e9dd --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.12/Oracle-DeusFinance-13m.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.12; + +interface IERC20 { + function balanceOf(address account) external view returns (uint256); + + function totalSupply() external view returns (uint256); +} + +contract Oracle { + IERC20 public dei; + IERC20 public usdc; + IERC20 public pair; + + constructor( + IERC20 dei_, + IERC20 usdc_, + IERC20 pair_ + ) { + dei = dei_; + usdc = usdc_; + pair = pair_; + } + + function getPrice() external view returns (uint256) { + return + ((dei.balanceOf(address(pair)) + (usdc.balanceOf(address(pair)) * 1e12)) * + 1e18) / pair.totalSupply(); + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/JAY.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/JAY.sol new file mode 100644 index 000000000..83d5de874 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/JAY.sol @@ -0,0 +1,1343 @@ +/** + *Submitted for verification at Etherscan.io on 2022-07-19 +*/ + +// Sources flattened with hardhat v2.9.1 https://hardhat.org + +// File @openzeppelin/contracts/utils/math/SafeMath.sol@v4.7.0 + + +// OpenZeppelin Contracts (last updated v4.6.0) (utils/math/SafeMath.sol) + +pragma solidity ^0.8.0; + +// CAUTION +// This version of SafeMath should only be used with Solidity 0.8 or later, +// because it relies on the compiler's built in overflow checks. + +/** + * @dev Wrappers over Solidity's arithmetic operations. + * + * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler + * now has built in overflow checking. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the subtraction of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + return a + b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return a - b; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + return a * b; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return a % b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {trySub}. + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b <= a, errorMessage); + return a - b; + } + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b > 0, errorMessage); + return a / b; + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting with custom message when dividing by zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryMod}. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b > 0, errorMessage); + return a % b; + } + } +} + + +// File @openzeppelin/contracts/token/ERC20/IERC20.sol@v4.7.0 + + +// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `from` to `to` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); +} + + +// File @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol@v4.7.0 + + +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} + + +// File @openzeppelin/contracts/utils/Context.sol@v4.7.0 + + +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + + +// File @openzeppelin/contracts/token/ERC20/ERC20.sol@v4.7.0 + + +// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.0; + + + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract ERC20 is Context, IERC20, IERC20Metadata { + mapping(address => uint256) private _balances; + + mapping(address => mapping(address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Sets the values for {name} and {symbol}. + * + * The default value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual override returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address to, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + * - the caller must have allowance for ``from``'s tokens of at least + * `amount`. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual override returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, allowance(owner, spender) + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + address owner = _msgSender(); + uint256 currentAllowance = allowance(owner, spender); + require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + + return true; + } + + /** + * @dev Moves `amount` of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + */ + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(from, to, amount); + + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + } + _balances[to] += amount; + + emit Transfer(from, to, amount); + + _afterTokenTransfer(from, to, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + _balances[account] += amount; + emit Transfer(address(0), account, amount); + + _afterTokenTransfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + _balances[account] = accountBalance - amount; + } + _totalSupply -= amount; + + emit Transfer(account, address(0), amount); + + _afterTokenTransfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `amount`. + * + * Does not update the allowance amount in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, "ERC20: insufficient allowance"); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} + + /** + * @dev Hook that is called after any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * has been transferred to `to`. + * - when `from` is zero, `amount` tokens have been minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens have been burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} +} + + +// File @openzeppelin/contracts/utils/introspection/IERC165.sol@v4.7.0 + + +// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC165 standard, as defined in the + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others ({ERC165Checker}). + * + * For an implementation, see {ERC165}. + */ +interface IERC165 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} + + +// File @openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol@v4.7.0 + + +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol) + +pragma solidity ^0.8.0; + +/** + * @dev _Available since v3.1._ + */ +interface IERC1155Receiver is IERC165 { + /** + * @dev Handles the receipt of a single ERC1155 token type. This function is + * called at the end of a `safeTransferFrom` after the balance has been updated. + * + * NOTE: To accept the transfer, this must return + * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` + * (i.e. 0xf23a6e61, or its own function selector). + * + * @param operator The address which initiated the transfer (i.e. msg.sender) + * @param from The address which previously owned the token + * @param id The ID of the token being transferred + * @param value The amount of tokens being transferred + * @param data Additional data with no specified format + * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed + */ + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4); + + /** + * @dev Handles the receipt of a multiple ERC1155 token types. This function + * is called at the end of a `safeBatchTransferFrom` after the balances have + * been updated. + * + * NOTE: To accept the transfer(s), this must return + * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` + * (i.e. 0xbc197c81, or its own function selector). + * + * @param operator The address which initiated the batch transfer (i.e. msg.sender) + * @param from The address which previously owned the token + * @param ids An array containing ids of each token being transferred (order and length must match values array) + * @param values An array containing amounts of each token being transferred (order and length must match ids array) + * @param data Additional data with no specified format + * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed + */ + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4); +} + + +// File @chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol@v0.4.1 + + +pragma solidity ^0.8.0; + +interface AggregatorV3Interface { + function decimals() external view returns (uint8); + + function description() external view returns (string memory); + + function version() external view returns (uint256); + + // getRoundData and latestRoundData should both raise "No data present" + // if they do not have data to report, instead of returning unset values + // which could be misinterpreted as actual reported values. + function getRoundData(uint80 _roundId) + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); +} + + +// File @openzeppelin/contracts/access/Ownable.sol@v4.7.0 + + +// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor() { + _transferOwnership(_msgSender()); + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + _checkOwner(); + _; + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if the sender is not the owner. + */ + function _checkOwner() internal view virtual { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _transferOwnership(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + + +// File contracts/JAY.sol + +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + + + +interface IERC721 { + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external; + + function transferFrom( + address from, + address to, + uint256 tokenId + ) external; +} + +interface IERC1155 { + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes calldata data + ) external; +} + +contract JAY is ERC20, Ownable { + using SafeMath for uint256; + AggregatorV3Interface internal priceFeed; + + address private dev; + uint256 public constant MIN = 1000; + bool private start = false; + bool private lockDev = false; + + uint256 private nftsBought; + uint256 private nftsSold; + + uint256 private buyNftFeeEth = 0.01 * 10**18; + uint256 private buyNftFeeJay = 10 * 10**18; + + uint256 private sellNftFeeEth = 0.001 * 10**18; + + uint256 private constant USD_PRICE_SELL = 2 * 10**18; + uint256 private constant USD_PRICE_BUY = 10 * 10**18; + + uint256 private nextFeeUpdate = block.timestamp.add(7 days); + + event Price(uint256 time, uint256 price); + + constructor() payable ERC20("JayPeggers", "JAY") { + require(msg.value == 2 * 10**18); + dev = msg.sender; + _mint(msg.sender, 2 * 10**18 * MIN); + emit Price(block.timestamp, JAYtoETH(1 * 10**18)); + priceFeed = AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419); //main + } + + function updateDevWallet(address _address) public onlyOwner { + require(lockDev == false); + dev = _address; + } + function lockDevWallet() public onlyOwner { + lockDev = true; + } + + function startJay() public onlyOwner { + start = true; + } + + // Buy NFTs from Vault + function buyNFTs( + address[] calldata erc721TokenAddress, + uint256[] calldata erc721Ids, + address[] calldata erc1155TokenAddress, + uint256[] calldata erc1155Ids, + uint256[] calldata erc1155Amounts + ) public payable { + uint256 total = erc721TokenAddress.length; + if (total != 0) buyERC721(erc721TokenAddress, erc721Ids); + + if (erc1155TokenAddress.length != 0) + total = total.add( + buyERC1155(erc1155TokenAddress, erc1155Ids, erc1155Amounts) + ); + + require( + msg.value >= (total).mul(buyNftFeeEth), + "You need to pay ETH more" + ); + (bool success, ) = dev.call{value: msg.value.div(2)}(""); + require(success, "ETH Transfer failed."); + _burn(msg.sender, total.mul(buyNftFeeJay)); + nftsBought += total; + + emit Price(block.timestamp, JAYtoETH(1 * 10**18)); + } + + function buyERC721(address[] calldata _tokenAddress, uint256[] calldata ids) + internal + { + for (uint256 id = 0; id < ids.length; id++) { + IERC721(_tokenAddress[id]).safeTransferFrom( + address(this), + msg.sender, + ids[id] + ); + } + } + + function buyERC1155( + address[] calldata _tokenAddress, + uint256[] calldata ids, + uint256[] calldata amounts + ) internal returns (uint256) { + uint256 amount = 0; + for (uint256 id = 0; id < ids.length; id++) { + amount = amount.add(amounts[id]); + IERC1155(_tokenAddress[id]).safeTransferFrom( + address(this), + msg.sender, + ids[id], + amounts[id], + "" + ); + } + return amount; + } + + // Sell NFTs (Buy Jay) + function buyJay( + address[] calldata erc721TokenAddress, + uint256[] calldata erc721Ids, + address[] calldata erc1155TokenAddress, + uint256[] calldata erc1155Ids, + uint256[] calldata erc1155Amounts + ) public payable { + require(start, "Not started!"); + uint256 total = erc721TokenAddress.length; + if (total != 0) buyJayWithERC721(erc721TokenAddress, erc721Ids); + + if (erc1155TokenAddress.length != 0) + total = total.add( + buyJayWithERC1155( + erc1155TokenAddress, + erc1155Ids, + erc1155Amounts + ) + ); + + if (total >= 100) + require( + msg.value >= (total).mul(sellNftFeeEth).div(2), + "You need to pay ETH more" + ); + else + require( + msg.value >= (total).mul(sellNftFeeEth), + "You need to pay ETH more" + ); + + _mint(msg.sender, ETHtoJAY(msg.value).mul(97).div(100)); + + (bool success, ) = dev.call{value: msg.value.div(34)}(""); + require(success, "ETH Transfer failed."); + + nftsSold += total; + + emit Price(block.timestamp, JAYtoETH(1 * 10**18)); + } + + function buyJayWithERC721( + address[] calldata _tokenAddress, + uint256[] calldata ids + ) internal { + for (uint256 id = 0; id < ids.length; id++) { + IERC721(_tokenAddress[id]).transferFrom( + msg.sender, + address(this), + ids[id] + ); + } + } + + function buyJayWithERC1155( + address[] calldata _tokenAddress, + uint256[] calldata ids, + uint256[] calldata amounts + ) internal returns (uint256) { + uint256 amount = 0; + for (uint256 id = 0; id < ids.length; id++) { + amount = amount.add(amounts[id]); + IERC1155(_tokenAddress[id]).safeTransferFrom( + msg.sender, + address(this), + ids[id], + amounts[id], + "" + ); + } + return amount; + } + + // Sell Jay + function sell(uint256 value) public { + require(value > MIN, "Dude tf"); + + uint256 eth = JAYtoETH(value); + _burn(msg.sender, value); + + (bool success, ) = msg.sender.call{value: eth.mul(90).div(100)}(""); + require(success, "ETH Transfer failed."); + (bool success2, ) = dev.call{value: eth.div(33)}(""); + require(success2, "ETH Transfer failed."); + + emit Price(block.timestamp, JAYtoETH(1 * 10**18)); + } + + // Buy Jay (No NFT) + function buyJayNoNFT() public payable { + require(msg.value > MIN, "must trade over min"); + require(start, "Not started!"); + + _mint(msg.sender, ETHtoJAY(msg.value).mul(85).div(100)); + + (bool success, ) = dev.call{value: msg.value.div(20)}(""); + require(success, "ETH Transfer failed."); + + emit Price(block.timestamp, JAYtoETH(1 * 10**18)); + } + + //utils + function getBuyJayNoNFT(uint256 amount) public view returns (uint256) { + return + amount.mul(totalSupply()).div(address(this).balance).mul(85).div( + 100 + ); + } + + function getBuyJayNFT(uint256 amount) public view returns (uint256) { + return + amount.mul(totalSupply()).div(address(this).balance).mul(97).div( + 100 + ); + } + + function JAYtoETH(uint256 value) public view returns (uint256) { + return (value * address(this).balance).div(totalSupply()); + } + + function ETHtoJAY(uint256 value) public view returns (uint256) { + return value.mul(totalSupply()).div(address(this).balance.sub(value)); + } + + // chainlink pricefeed / fee updater + function getFees() + public + view + returns ( + uint256, + uint256, + uint256, + uint256 + ) + { + return (sellNftFeeEth, buyNftFeeEth, buyNftFeeJay, nextFeeUpdate); + } + + function getTotals() + public + view + returns ( + uint256, + uint256 + ) + { + return (nftsBought, nftsSold); + } + + + function updateFees() + public + returns ( + uint256, + uint256, + uint256, + uint256 + ) + { + ( + uint80 roundID, + int256 price, + uint256 startedAt, + uint256 timeStamp, + uint80 answeredInRound + ) = priceFeed.latestRoundData(); + uint256 _price = uint256(price).mul(1 * 10**10); + require( + timeStamp > nextFeeUpdate, + "Fee update every 24 hrs" + ); + + uint256 _sellNftFeeEth; + if (_price > USD_PRICE_SELL) { + uint256 _p = _price.div(USD_PRICE_SELL); + _sellNftFeeEth = uint256(1 * 10**18).div(_p); + } else { + _sellNftFeeEth = USD_PRICE_SELL.div(_price); + } + + require( + owner() == msg.sender || + (sellNftFeeEth.div(2) < _sellNftFeeEth && + sellNftFeeEth.mul(150) > _sellNftFeeEth), + "Fee swing too high" + ); + + sellNftFeeEth = _sellNftFeeEth; + + if (_price > USD_PRICE_BUY) { + uint256 _p = _price.div(USD_PRICE_BUY); + buyNftFeeEth = uint256(1 * 10**18).div(_p); + } else { + buyNftFeeEth = USD_PRICE_BUY.div(_price); + } + buyNftFeeJay = ETHtoJAY(buyNftFeeEth); + + nextFeeUpdate = timeStamp.add(24 hours); + return (sellNftFeeEth, buyNftFeeEth, buyNftFeeJay, nextFeeUpdate); + } + + function getLatestPrice() public view returns (int256) { + ( + uint80 roundID, + int256 price, + uint256 startedAt, + uint256 timeStamp, + uint80 answeredInRound + ) = priceFeed.latestRoundData(); + return price; + } + + //receiver helpers + function deposit() public payable {} + + receive() external payable {} + + fallback() external payable {} + + function onERC1155Received( + address, + address from, + uint256 id, + uint256 amount, + bytes calldata data + ) external pure returns (bytes4) { + return IERC1155Receiver.onERC1155Received.selector; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/Keep3rV2Oracle.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/Keep3rV2Oracle.sol new file mode 100644 index 000000000..9d0f9d8ad --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/Keep3rV2Oracle.sol @@ -0,0 +1,367 @@ +/** + *Submitted for verification at Etherscan.io on 2021-05-11 +*/ + +/** + *Submitted for verification at Etherscan.io on 2021-04-19 +*/ + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.2; + +interface IUniswapV2Pair { + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + function price0CumulativeLast() external view returns (uint); + function price1CumulativeLast() external view returns (uint); + function token0() external view returns (address); + function token1() external view returns (address); +} + +interface IKeep3rV1 { + function keepers(address keeper) external returns (bool); + function KPRH() external view returns (IKeep3rV1Helper); + function receipt(address credit, address keeper, uint amount) external; +} + +interface IKeep3rV1Helper { + function getQuoteLimit(uint gasUsed) external view returns (uint); +} + +// sliding oracle that uses observations collected to provide moving price averages in the past +contract Keep3rV2Oracle { + + constructor(address _pair) { + _factory = msg.sender; + pair = _pair; + (,,uint32 timestamp) = IUniswapV2Pair(_pair).getReserves(); + uint112 _price0CumulativeLast = uint112(IUniswapV2Pair(_pair).price0CumulativeLast() * e10 / Q112); + uint112 _price1CumulativeLast = uint112(IUniswapV2Pair(_pair).price1CumulativeLast() * e10 / Q112); + observations[length++] = Observation(timestamp, _price0CumulativeLast, _price1CumulativeLast); + } + + struct Observation { + uint32 timestamp; + uint112 price0Cumulative; + uint112 price1Cumulative; + } + + modifier factory() { + require(msg.sender == _factory, "!F"); + _; + } + + Observation[65535] public observations; + uint16 public length; + + address immutable _factory; + address immutable public pair; + // this is redundant with granularity and windowSize, but stored for gas savings & informational purposes. + uint constant periodSize = 1800; + uint Q112 = 2**112; + uint e10 = 10**18; + + // Pre-cache slots for cheaper oracle writes + function cache(uint size) external { + uint _length = length+size; + for (uint i = length; i < _length; i++) observations[i].timestamp = 1; + } + + // update the current feed for free + function update() external factory returns (bool) { + return _update(); + } + + function updateable() external view returns (bool) { + Observation memory _point = observations[length-1]; + (,, uint timestamp) = IUniswapV2Pair(pair).getReserves(); + uint timeElapsed = timestamp - _point.timestamp; + return timeElapsed > periodSize; + } + + function _update() internal returns (bool) { + Observation memory _point = observations[length-1]; + (,, uint32 timestamp) = IUniswapV2Pair(pair).getReserves(); + uint32 timeElapsed = timestamp - _point.timestamp; + if (timeElapsed > periodSize) { + uint112 _price0CumulativeLast = uint112(IUniswapV2Pair(pair).price0CumulativeLast() * e10 / Q112); + uint112 _price1CumulativeLast = uint112(IUniswapV2Pair(pair).price1CumulativeLast() * e10 / Q112); + observations[length++] = Observation(timestamp, _price0CumulativeLast, _price1CumulativeLast); + return true; + } + return false; + } + + function _computeAmountOut(uint start, uint end, uint elapsed, uint amountIn) internal view returns (uint amountOut) { + amountOut = amountIn * (end - start) / e10 / elapsed; + } + + function current(address tokenIn, uint amountIn, address tokenOut) external view returns (uint amountOut, uint lastUpdatedAgo) { + (address token0,) = tokenIn < tokenOut ? (tokenIn, tokenOut) : (tokenOut, tokenIn); + + Observation memory _observation = observations[length-1]; + uint price0Cumulative = IUniswapV2Pair(pair).price0CumulativeLast() * e10 / Q112; + uint price1Cumulative = IUniswapV2Pair(pair).price1CumulativeLast() * e10 / Q112; + (,,uint timestamp) = IUniswapV2Pair(pair).getReserves(); + + // Handle edge cases where we have no updates, will revert on first reading set + if (timestamp == _observation.timestamp) { + _observation = observations[length-2]; + } + + uint timeElapsed = timestamp - _observation.timestamp; + timeElapsed = timeElapsed == 0 ? 1 : timeElapsed; + if (token0 == tokenIn) { + amountOut = _computeAmountOut(_observation.price0Cumulative, price0Cumulative, timeElapsed, amountIn); + } else { + amountOut = _computeAmountOut(_observation.price1Cumulative, price1Cumulative, timeElapsed, amountIn); + } + lastUpdatedAgo = timeElapsed; + } + + function quote(address tokenIn, uint amountIn, address tokenOut, uint points) external view returns (uint amountOut, uint lastUpdatedAgo) { + (address token0,) = tokenIn < tokenOut ? (tokenIn, tokenOut) : (tokenOut, tokenIn); + + uint priceAverageCumulative = 0; + uint _length = length-1; + uint i = _length - points; + Observation memory currentObservation; + Observation memory nextObservation; + + uint nextIndex = 0; + if (token0 == tokenIn) { + for (; i < _length; i++) { + nextIndex = i+1; + currentObservation = observations[i]; + nextObservation = observations[nextIndex]; + priceAverageCumulative += _computeAmountOut( + currentObservation.price0Cumulative, + nextObservation.price0Cumulative, + nextObservation.timestamp - currentObservation.timestamp, amountIn); + } + } else { + for (; i < _length; i++) { + nextIndex = i+1; + currentObservation = observations[i]; + nextObservation = observations[nextIndex]; + priceAverageCumulative += _computeAmountOut( + currentObservation.price1Cumulative, + nextObservation.price1Cumulative, + nextObservation.timestamp - currentObservation.timestamp, amountIn); + } + } + amountOut = priceAverageCumulative / points; + + (,,uint timestamp) = IUniswapV2Pair(pair).getReserves(); + lastUpdatedAgo = timestamp - nextObservation.timestamp; + } + + function sample(address tokenIn, uint amountIn, address tokenOut, uint points, uint window) external view returns (uint[] memory prices, uint lastUpdatedAgo) { + (address token0,) = tokenIn < tokenOut ? (tokenIn, tokenOut) : (tokenOut, tokenIn); + prices = new uint[](points); + + if (token0 == tokenIn) { + { + uint _length = length-1; + uint i = _length - (points * window); + uint _index = 0; + Observation memory nextObservation; + for (; i < _length; i+=window) { + Observation memory currentObservation; + currentObservation = observations[i]; + nextObservation = observations[i + window]; + prices[_index] = _computeAmountOut( + currentObservation.price0Cumulative, + nextObservation.price0Cumulative, + nextObservation.timestamp - currentObservation.timestamp, amountIn); + _index = _index + 1; + } + + (,,uint timestamp) = IUniswapV2Pair(pair).getReserves(); + lastUpdatedAgo = timestamp - nextObservation.timestamp; + } + } else { + { + uint _length = length-1; + uint i = _length - (points * window); + uint _index = 0; + Observation memory nextObservation; + for (; i < _length; i+=window) { + Observation memory currentObservation; + currentObservation = observations[i]; + nextObservation = observations[i + window]; + prices[_index] = _computeAmountOut( + currentObservation.price1Cumulative, + nextObservation.price1Cumulative, + nextObservation.timestamp - currentObservation.timestamp, amountIn); + _index = _index + 1; + } + + (,,uint timestamp) = IUniswapV2Pair(pair).getReserves(); + lastUpdatedAgo = timestamp - nextObservation.timestamp; + } + } + } +} + +contract Keep3rV2OracleFactory { + + function pairForSushi(address tokenA, address tokenB) internal pure returns (address pair) { + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + pair = address(uint160(uint256(keccak256(abi.encodePacked( + hex'ff', + 0xc35DADB65012eC5796536bD9864eD8773aBc74C4, + keccak256(abi.encodePacked(token0, token1)), + hex'e18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303' // init code hash + ))))); + } + + function pairForUni(address tokenA, address tokenB) internal pure returns (address pair) { + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + pair = address(uint160(uint256(keccak256(abi.encodePacked( + hex'ff', + 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f, + keccak256(abi.encodePacked(token0, token1)), + hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash + ))))); + } + + modifier keeper() { + require(KP3R.keepers(msg.sender), "!K"); + _; + } + + modifier upkeep() { + uint _gasUsed = gasleft(); + require(KP3R.keepers(msg.sender), "!K"); + _; + uint _received = KP3R.KPRH().getQuoteLimit(_gasUsed - gasleft()); + KP3R.receipt(address(KP3R), msg.sender, _received); + } + + address public governance; + address public pendingGovernance; + + /** + * @notice Allows governance to change governance (for future upgradability) + * @param _governance new governance address to set + */ + function setGovernance(address _governance) external { + require(msg.sender == governance, "!G"); + pendingGovernance = _governance; + } + + /** + * @notice Allows pendingGovernance to accept their role as governance (protection pattern) + */ + function acceptGovernance() external { + require(msg.sender == pendingGovernance, "!pG"); + governance = pendingGovernance; + } + + IKeep3rV1 public constant KP3R = IKeep3rV1(0x1cEB5cB57C4D4E2b2433641b95Dd330A33185A44); + + address[] internal _pairs; + mapping(address => Keep3rV2Oracle) public feeds; + + function pairs() external view returns (address[] memory) { + return _pairs; + } + + constructor() { + governance = msg.sender; + } + + function update(address pair) external keeper returns (bool) { + return feeds[pair].update(); + } + + function byteCode(address pair) external pure returns (bytes memory bytecode) { + bytecode = abi.encodePacked(type(Keep3rV2Oracle).creationCode, abi.encode(pair)); + } + + function deploy(address pair) external returns (address feed) { + require(msg.sender == governance, "!G"); + require(address(feeds[pair]) == address(0), 'PE'); + bytes memory bytecode = abi.encodePacked(type(Keep3rV2Oracle).creationCode, abi.encode(pair)); + bytes32 salt = keccak256(abi.encodePacked(pair)); + assembly { + feed := create2(0, add(bytecode, 0x20), mload(bytecode), salt) + if iszero(extcodesize(feed)) { + revert(0, 0) + } + } + feeds[pair] = Keep3rV2Oracle(feed); + _pairs.push(pair); + } + + function work() external upkeep { + require(workable(), "!W"); + for (uint i = 0; i < _pairs.length; i++) { + feeds[_pairs[i]].update(); + } + } + + function work(address pair) external upkeep { + require(feeds[pair].update(), "!W"); + } + + function workForFree() external { + for (uint i = 0; i < _pairs.length; i++) { + feeds[_pairs[i]].update(); + } + } + + function workForFree(address pair) external { + feeds[pair].update(); + } + + function cache(uint size) external { + for (uint i = 0; i < _pairs.length; i++) { + feeds[_pairs[i]].cache(size); + } + } + + function cache(address pair, uint size) external { + feeds[pair].cache(size); + } + + function workable() public view returns (bool canWork) { + canWork = true; + for (uint i = 0; i < _pairs.length; i++) { + if (!feeds[_pairs[i]].updateable()) { + canWork = false; + } + } + } + + function workable(address pair) public view returns (bool) { + return feeds[pair].updateable(); + } + + function sample(address tokenIn, uint amountIn, address tokenOut, uint points, uint window, bool sushiswap) external view returns (uint[] memory prices, uint lastUpdatedAgo) { + address _pair = sushiswap ? pairForSushi(tokenIn, tokenOut) : pairForUni(tokenIn, tokenOut); + return feeds[_pair].sample(tokenIn, amountIn, tokenOut, points, window); + } + + function sample(address pair, address tokenIn, uint amountIn, address tokenOut, uint points, uint window) external view returns (uint[] memory prices, uint lastUpdatedAgo) { + return feeds[pair].sample(tokenIn, amountIn, tokenOut, points, window); + } + + function quote(address tokenIn, uint amountIn, address tokenOut, uint points, bool sushiswap) external view returns (uint amountOut, uint lastUpdatedAgo) { + address _pair = sushiswap ? pairForSushi(tokenIn, tokenOut) : pairForUni(tokenIn, tokenOut); + return feeds[_pair].quote(tokenIn, amountIn, tokenOut, points); + } + + function quote(address pair, address tokenIn, uint amountIn, address tokenOut, uint points) external view returns (uint amountOut, uint lastUpdatedAgo) { + return feeds[pair].quote(tokenIn, amountIn, tokenOut, points); + } + + function current(address tokenIn, uint amountIn, address tokenOut, bool sushiswap) external view returns (uint amountOut, uint lastUpdatedAgo) { + address _pair = sushiswap ? pairForSushi(tokenIn, tokenOut) : pairForUni(tokenIn, tokenOut); + return feeds[_pair].current(tokenIn, amountIn, tokenOut); + } + + function current(address pair, address tokenIn, uint amountIn, address tokenOut) external view returns (uint amountOut, uint lastUpdatedAgo) { + return feeds[pair].current(tokenIn, amountIn, tokenOut); + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/PancakeOracle-PloutozFinance-365k.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/PancakeOracle-PloutozFinance-365k.sol new file mode 100644 index 000000000..bf84cf99b --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/PancakeOracle-PloutozFinance-365k.sol @@ -0,0 +1,443 @@ +/** + *Submitted for verification at BscScan.com on 2021-08-12 +*/ + +// Sources flattened with hardhat v2.6.0 https://hardhat.org + +// File @openzeppelin/contracts/utils/math/SafeMath.sol@v4.2.0 + +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +// CAUTION +// This version of SafeMath should only be used with Solidity 0.8 or later, +// because it relies on the compiler's built in overflow checks. + +/** + * @dev Wrappers over Solidity's arithmetic operations. + * + * NOTE: `SafeMath` is no longer needed starting with Solidity 0.8. The compiler + * now has built in overflow checking. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the substraction of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + return a + b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return a - b; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + return a * b; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return a % b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {trySub}. + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b <= a, errorMessage); + return a - b; + } + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b > 0, errorMessage); + return a / b; + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting with custom message when dividing by zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryMod}. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b > 0, errorMessage); + return a % b; + } + } +} + + +// File @openzeppelin/contracts/utils/Context.sol@v4.2.0 + + +pragma solidity ^0.8.0; + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + + +// File @openzeppelin/contracts/access/Ownable.sol@v4.2.0 + + +pragma solidity ^0.8.0; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor() { + _setOwner(_msgSender()); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _setOwner(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _setOwner(newOwner); + } + + function _setOwner(address newOwner) private { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + + +// File contracts/pancake/IPancakePair.sol + +interface IPancakePair { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint value); + + function name() external pure returns (string memory); + function symbol() external pure returns (string memory); + function decimals() external pure returns (uint8); + function totalSupply() external view returns (uint); + function balanceOf(address owner) external view returns (uint); + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + function transfer(address to, uint value) external returns (bool); + function transferFrom(address from, address to, uint value) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + function PERMIT_TYPEHASH() external pure returns (bytes32); + function nonces(address owner) external view returns (uint); + + function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; + + event Mint(address indexed sender, uint amount0, uint amount1); + event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); + event Swap( + address indexed sender, + uint amount0In, + uint amount1In, + uint amount0Out, + uint amount1Out, + address indexed to + ); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint); + function factory() external view returns (address); + function token0() external view returns (address); + function token1() external view returns (address); + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + function price0CumulativeLast() external view returns (uint); + function price1CumulativeLast() external view returns (uint); + function kLast() external view returns (uint); + + function mint(address to) external returns (uint liquidity); + function burn(address to) external returns (uint amount0, uint amount1); + function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; + function skim(address to) external; + function sync() external; + + function initialize(address, address) external; +} + + +// File contracts/PancakeOracle.sol + +pragma experimental ABIEncoderV2; + + + +interface IPriceFeedsExt { + function latestAnswer() external view returns (uint256); +} + +contract PancakeOracle is IPriceFeedsExt, Ownable { + using SafeMath for uint112; + using SafeMath for uint256; + + address public pairRef; + uint256 public baseTokenIndex; + + constructor(address _pairRef, uint256 _baseTokenIndex) { + pairRef = _pairRef; + baseTokenIndex = _baseTokenIndex; + } + + /** + * @return _price + */ + function latestAnswer() external view override returns (uint256 _price) { + ( + uint112 _reserve0, + uint112 _reserve1, + uint32 _blockTimestampLast + ) = IPancakePair(pairRef).getReserves(); + + _price; + if (baseTokenIndex == 1) { + _price = _reserve1.mul(1e18).div(_reserve0); + } else { + _price = _reserve0.mul(1e18).div(_reserve1); + } + } + + /** + * @return _timestamp + */ + function latestTimestamp() external view returns (uint256 _timestamp) { + _timestamp = block.timestamp; + } + + function setPairRefAddress(address _ref) public onlyOwner { + pairRef = _ref; + } + + function setBaseTokenIndex(uint256 _baseTokenIndex) public onlyOwner { + baseTokenIndex = _baseTokenIndex; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/SmartChefFactory.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/SmartChefFactory.sol new file mode 100644 index 000000000..22d5f2a6e --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/SmartChefFactory.sol @@ -0,0 +1,1064 @@ +/** + *Submitted for verification at BscScan.com on 2021-05-05 +*/ + +// File: @openzeppelin/contracts/utils/Context.sol + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//import "hardhat/console.sol"; + +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + +// File: @openzeppelin/contracts/access/Ownable.sol + +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor() { + _setOwner(_msgSender()); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _setOwner(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _setOwner(newOwner); + } + + function _setOwner(address newOwner) private { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + +// File: @openzeppelin/contracts/utils/ReentrancyGuard.sol + +/** + * @dev Contract module that helps prevent reentrant calls to a function. + * + * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier + * available, which can be applied to functions to make sure there are no nested + * (reentrant) calls to them. + * + * Note that because there is a single `nonReentrant` guard, functions marked as + * `nonReentrant` may not call one another. This can be worked around by making + * those functions `private`, and then adding `external` `nonReentrant` entry + * points to them. + * + * TIP: If you would like to learn more about reentrancy and alternative ways + * to protect against it, check out our blog post + * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. + */ +abstract contract ReentrancyGuard { + // Booleans are more expensive than uint256 or any type that takes up a full + // word because each write operation emits an extra SLOAD to first read the + // slot's contents, replace the bits taken up by the boolean, and then write + // back. This is the compiler's defense against contract upgrades and + // pointer aliasing, and it cannot be disabled. + + // The values being non-zero value makes deployment a bit more expensive, + // but in exchange the refund on every call to nonReentrant will be lower in + // amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to + // increase the likelihood of the full refund coming into effect. + uint256 private constant _NOT_ENTERED = 1; + uint256 private constant _ENTERED = 2; + + uint256 private _status; + + constructor() { + _status = _NOT_ENTERED; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and make it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + // On the first call to nonReentrant, _notEntered will be true + require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + + // Any calls to nonReentrant after this point will fail + _status = _ENTERED; + + _; + + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = _NOT_ENTERED; + } +} + +// File: @openzeppelin/contracts/utils/Address.sol + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + assembly { + size := extcodesize(account) + } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + (bool success, bytes memory returndata) = target.delegatecall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + function _verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) private pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +// File: "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using Address for address; + + function safeTransfer( + IERC20 token, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Deprecated. This function has issues similar to the ones found in + * {IERC20-approve}, and its usage is discouraged. + * + * Whenever possible, use {safeIncreaseAllowance} and + * {safeDecreaseAllowance} instead. + */ + function safeApprove( + IERC20 token, + address spender, + uint256 value + ) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + require( + (value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + uint256 newAllowance = token.allowance(address(this), spender) + value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + unchecked { + uint256 oldAllowance = token.allowance(address(this), spender); + require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); + uint256 newAllowance = oldAllowance - value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); + if (returndata.length > 0) { + // Return data is optional + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +// File: IPancakeProfile.sol + +/** + * @title IPancakeProfile + */ +interface IPancakeProfile { + function createProfile( + uint256 _teamId, + address _nftAddress, + uint256 _tokenId + ) external; + + function increaseUserPoints( + address _userAddress, + uint256 _numberPoints, + uint256 _campaignId + ) external; + + function removeUserPoints(address _userAddress, uint256 _numberPoints) external; + + function addNftAddress(address _nftAddress) external; + + function addTeam(string calldata _teamName, string calldata _teamDescription) external; + + function getUserProfile(address _userAddress) + external + view + returns ( + uint256, + uint256, + uint256, + address, + uint256, + bool + ); + + function getUserStatus(address _userAddress) external view returns (bool); + + function getTeamProfile(uint256 _teamId) + external + view + returns ( + string memory, + string memory, + uint256, + uint256, + bool + ); +} + +// File: contracts/SmartChefInitializable.sol + +contract SmartChefInitializable is Ownable, ReentrancyGuard { + using SafeERC20 for IERC20Metadata; + + // The address of the smart chef factory + address public immutable SMART_CHEF_FACTORY; + + // Whether a limit is set for users + bool public userLimit; + + // Whether it is initialized + bool public isInitialized; + + // Accrued token per share + uint256 public accTokenPerShare; + + // The block number when CAKE mining ends. + uint256 public bonusEndBlock; + + // The block number when CAKE mining starts. + uint256 public startBlock; + + // The block number of the last pool update + uint256 public lastRewardBlock; + + // The pool limit (0 if none) + uint256 public poolLimitPerUser; + + // Block numbers available for user limit (after start block) + uint256 public numberBlocksForUserLimit; + + // Pancake profile + IPancakeProfile public immutable pancakeProfile; + + // Pancake Profile is requested + bool public pancakeProfileIsRequested; + + // Pancake Profile points threshold + uint256 public pancakeProfileThresholdPoints; + + // CAKE tokens created per block. + uint256 public rewardPerBlock; + + // The precision factor + uint256 public PRECISION_FACTOR; + + // The reward token + IERC20Metadata public rewardToken; + + // The staked token + IERC20Metadata public stakedToken; + + // Info of each user that stakes tokens (stakedToken) + mapping(address => UserInfo) public userInfo; + + struct UserInfo { + uint256 amount; // How many staked tokens the user has provided + uint256 rewardDebt; // Reward debt + } + + event Deposit(address indexed user, uint256 amount); + event EmergencyWithdraw(address indexed user, uint256 amount); + event NewStartAndEndBlocks(uint256 startBlock, uint256 endBlock); + event NewRewardPerBlock(uint256 rewardPerBlock); + event NewPoolLimit(uint256 poolLimitPerUser); + event RewardsStop(uint256 blockNumber); + event TokenRecovery(address indexed token, uint256 amount); + event Withdraw(address indexed user, uint256 amount); + event UpdateProfileAndThresholdPointsRequirement(bool isProfileRequested, uint256 thresholdPoints); + + /** + * @notice Constructor + * @param _pancakeProfile: Pancake Profile address + * @param _pancakeProfileIsRequested: Pancake Profile is requested + * @param _pancakeProfileThresholdPoints: Pancake Profile need threshold points + */ + constructor( + address _pancakeProfile, + bool _pancakeProfileIsRequested, + uint256 _pancakeProfileThresholdPoints + ) { + SMART_CHEF_FACTORY = msg.sender; + + // Call to verify the address is correct + IPancakeProfile(_pancakeProfile).getTeamProfile(1); + pancakeProfile = IPancakeProfile(_pancakeProfile); + + // if pancakeProfile is requested + pancakeProfileIsRequested = _pancakeProfileIsRequested; + + // pancakeProfile threshold points when profile & points are requested + pancakeProfileThresholdPoints = _pancakeProfileThresholdPoints; + } + + /* + * @notice Initialize the contract + * @param _stakedToken: staked token address + * @param _rewardToken: reward token address + * @param _rewardPerBlock: reward per block (in rewardToken) + * @param _startBlock: start block + * @param _bonusEndBlock: end block + * @param _poolLimitPerUser: pool limit per user in stakedToken (if any, else 0) + * @param _numberBlocksForUserLimit: block numbers available for user limit (after start block) + * @param _admin: admin address with ownership + */ + function initialize( + IERC20Metadata _stakedToken, + IERC20Metadata _rewardToken, + uint256 _rewardPerBlock, + uint256 _startBlock, + uint256 _bonusEndBlock, + uint256 _poolLimitPerUser, + uint256 _numberBlocksForUserLimit, + address _admin + ) external { + require(!isInitialized, "Already initialized"); + require(msg.sender == SMART_CHEF_FACTORY, "Not factory"); + + // Make this contract initialized + isInitialized = true; + + stakedToken = _stakedToken; + rewardToken = _rewardToken; + rewardPerBlock = _rewardPerBlock; + startBlock = _startBlock; + bonusEndBlock = _bonusEndBlock; + + if (_poolLimitPerUser > 0) { + userLimit = true; + poolLimitPerUser = _poolLimitPerUser; + numberBlocksForUserLimit = _numberBlocksForUserLimit; + } + + uint256 decimalsRewardToken = uint256(rewardToken.decimals()); + require(decimalsRewardToken < 30, "Must be less than 30"); + + PRECISION_FACTOR = uint256(10**(uint256(30) - decimalsRewardToken)); + + // Set the lastRewardBlock as the startBlock + lastRewardBlock = startBlock; + + // Transfer ownership to the admin address who becomes owner of the contract + transferOwnership(_admin); + } + + /* + * @notice Deposit staked tokens and collect reward tokens (if any) + * @param _amount: amount to withdraw (in rewardToken) + */ + function deposit(uint256 _amount) external nonReentrant { + UserInfo storage user = userInfo[msg.sender]; + + // Checks whether the user has an active profile + require( + (!pancakeProfileIsRequested && pancakeProfileThresholdPoints == 0) || + pancakeProfile.getUserStatus(msg.sender), + "Deposit: Must have an active profile" + ); + + uint256 numberUserPoints = 0; + + if (pancakeProfileThresholdPoints > 0) { + (, numberUserPoints, , , , ) = pancakeProfile.getUserProfile(msg.sender); + } + + require( + pancakeProfileThresholdPoints == 0 || numberUserPoints >= pancakeProfileThresholdPoints, + "Deposit: User has not enough points" + ); + + userLimit = hasUserLimit(); + + require(!userLimit || ((_amount + user.amount) <= poolLimitPerUser), "Deposit: Amount above limit"); + + _updatePool(); + + if (user.amount > 0) { + uint256 pending = (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + if (pending > 0) { + rewardToken.safeTransfer(address(msg.sender), pending); + } + } + + if (_amount > 0) { + user.amount = user.amount + _amount; + stakedToken.safeTransferFrom(address(msg.sender), address(this), _amount); + } + + user.rewardDebt = (user.amount * accTokenPerShare) / PRECISION_FACTOR; + + emit Deposit(msg.sender, _amount); + } + + /* + * @notice Withdraw staked tokens and collect reward tokens + * @param _amount: amount to withdraw (in rewardToken) + */ + function withdraw(uint256 _amount) external nonReentrant { + UserInfo storage user = userInfo[msg.sender]; + require(user.amount >= _amount, "Amount to withdraw too high"); + + _updatePool(); + + uint256 pending = (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + + if (_amount > 0) { + user.amount = user.amount - _amount; + stakedToken.safeTransfer(address(msg.sender), _amount); + } + + if (pending > 0) { + rewardToken.safeTransfer(address(msg.sender), pending); + } + + user.rewardDebt = (user.amount * accTokenPerShare) / PRECISION_FACTOR; + + emit Withdraw(msg.sender, _amount); + } + + /* + * @notice Withdraw staked tokens without caring about rewards rewards + * @dev Needs to be for emergency. + */ + function emergencyWithdraw() external nonReentrant { + UserInfo storage user = userInfo[msg.sender]; + uint256 amountToTransfer = user.amount; + user.amount = 0; + user.rewardDebt = 0; + + if (amountToTransfer > 0) { + stakedToken.safeTransfer(address(msg.sender), amountToTransfer); + } + + emit EmergencyWithdraw(msg.sender, user.amount); + } + + /* + * @notice Stop rewards + * @dev Only callable by owner. Needs to be for emergency. + */ + function emergencyRewardWithdraw(uint256 _amount) external onlyOwner { + rewardToken.safeTransfer(address(msg.sender), _amount); + } + + /** + * @notice Allows the owner to recover tokens sent to the contract by mistake + * @param _token: token address + * @dev Callable by owner + */ + function recoverToken(address _token) external onlyOwner { + require(_token != address(stakedToken), "Operations: Cannot recover staked token"); + require(_token != address(rewardToken), "Operations: Cannot recover reward token"); + + uint256 balance = IERC20Metadata(_token).balanceOf(address(this)); + require(balance != 0, "Operations: Cannot recover zero balance"); + + IERC20Metadata(_token).safeTransfer(address(msg.sender), balance); + + emit TokenRecovery(_token, balance); + } + + /* + * @notice Stop rewards + * @dev Only callable by owner + */ + function stopReward() external onlyOwner { + bonusEndBlock = block.number; + } + + /* + * @notice Update pool limit per user + * @dev Only callable by owner. + * @param _userLimit: whether the limit remains forced + * @param _poolLimitPerUser: new pool limit per user + */ + function updatePoolLimitPerUser(bool _userLimit, uint256 _poolLimitPerUser) external onlyOwner { + require(userLimit, "Must be set"); + if (_userLimit) { + require(_poolLimitPerUser > poolLimitPerUser, "New limit must be higher"); + poolLimitPerUser = _poolLimitPerUser; + } else { + userLimit = _userLimit; + poolLimitPerUser = 0; + } + emit NewPoolLimit(poolLimitPerUser); + } + + /* + * @notice Update reward per block + * @dev Only callable by owner. + * @param _rewardPerBlock: the reward per block + */ + function updateRewardPerBlock(uint256 _rewardPerBlock) external onlyOwner { + require(block.number < startBlock, "Pool has started"); + rewardPerBlock = _rewardPerBlock; + emit NewRewardPerBlock(_rewardPerBlock); + } + + /** + * @notice It allows the admin to update start and end blocks + * @dev This function is only callable by owner. + * @param _startBlock: the new start block + * @param _bonusEndBlock: the new end block + */ + function updateStartAndEndBlocks(uint256 _startBlock, uint256 _bonusEndBlock) external onlyOwner { + require(block.number < startBlock, "Pool has started"); + require(_startBlock < _bonusEndBlock, "New startBlock must be lower than new endBlock"); + require(block.number < _startBlock, "New startBlock must be higher than current block"); + + startBlock = _startBlock; + bonusEndBlock = _bonusEndBlock; + + // Set the lastRewardBlock as the startBlock + lastRewardBlock = startBlock; + + emit NewStartAndEndBlocks(_startBlock, _bonusEndBlock); + } + + /** + * @notice It allows the admin to update profile and thresholdPoints' requirement. + * @dev This function is only callable by owner. + * @param _isRequested: the profile is requested + * @param _thresholdPoints: the threshold points + */ + function updateProfileAndThresholdPointsRequirement(bool _isRequested, uint256 _thresholdPoints) external onlyOwner { + require(_thresholdPoints >= 0, "Threshold points need to exceed 0"); + pancakeProfileIsRequested = _isRequested; + pancakeProfileThresholdPoints = _thresholdPoints; + emit UpdateProfileAndThresholdPointsRequirement(_isRequested, _thresholdPoints); + } + + /* + * @notice View function to see pending reward on frontend. + * @param _user: user address + * @return Pending reward for a given user + */ + function pendingReward(address _user) external view returns (uint256) { + UserInfo storage user = userInfo[_user]; + uint256 stakedTokenSupply = stakedToken.balanceOf(address(this)); + if (block.number > lastRewardBlock && stakedTokenSupply != 0) { + uint256 multiplier = _getMultiplier(lastRewardBlock, block.number); + uint256 cakeReward = multiplier * rewardPerBlock; + uint256 adjustedTokenPerShare = accTokenPerShare + (cakeReward * PRECISION_FACTOR) / stakedTokenSupply; + return (user.amount * adjustedTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + } else { + return (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + } + } + + /* + * @notice Update reward variables of the given pool to be up-to-date. + */ + function _updatePool() internal { + if (block.number <= lastRewardBlock) { + return; + } + + uint256 stakedTokenSupply = stakedToken.balanceOf(address(this)); + + if (stakedTokenSupply == 0) { + lastRewardBlock = block.number; + return; + } + + uint256 multiplier = _getMultiplier(lastRewardBlock, block.number); + uint256 cakeReward = multiplier * rewardPerBlock; + accTokenPerShare = accTokenPerShare + (cakeReward * PRECISION_FACTOR) / stakedTokenSupply; + lastRewardBlock = block.number; + } + + /* + * @notice Return reward multiplier over the given _from to _to block. + * @param _from: block to start + * @param _to: block to finish + */ + function _getMultiplier(uint256 _from, uint256 _to) internal view returns (uint256) { + if (_to <= bonusEndBlock) { + return _to - _from; + } else if (_from >= bonusEndBlock) { + return 0; + } else { + return bonusEndBlock - _from; + } + } + + /* + * @notice Return user limit is set or zero. + */ + function hasUserLimit() public view returns (bool) { + if (!userLimit || (block.number >= (startBlock + numberBlocksForUserLimit))) { + return false; + } + + return true; + } +} + +// File: contracts/SmartChefFactory.sol + +contract SmartChefFactory is Ownable { + event NewSmartChefContract(address indexed smartChef); + + constructor() { + // + } + + /* + * @notice Deploy the pool + * @param _stakedToken: staked token address + * @param _rewardToken: reward token address + * @param _rewardPerBlock: reward per block (in rewardToken) + * @param _startBlock: start block + * @param _endBlock: end block + * @param _poolLimitPerUser: pool limit per user in stakedToken (if any, else 0) + * @param _numberBlocksForUserLimit: block numbers available for user limit (after start block) + * @param _pancakeProfile: Pancake Profile address + * @param _pancakeProfileIsRequested: Pancake Profile is requested + * @param _pancakeProfileThresholdPoints: Pancake Profile need threshold points + * @param _admin: admin address with ownership + * @return address of new smart chef contract + */ + function deployPool( + IERC20Metadata _stakedToken, + IERC20Metadata _rewardToken, + uint256 _rewardPerBlock, + uint256 _startBlock, + uint256 _bonusEndBlock, + uint256 _poolLimitPerUser, + uint256 _numberBlocksForUserLimit, + address _pancakeProfile, + bool _pancakeProfileIsRequested, + uint256 _pancakeProfileThresholdPoints, + address _admin + ) external onlyOwner { + require(_stakedToken.totalSupply() >= 0); + require(_rewardToken.totalSupply() >= 0); + require(_stakedToken != _rewardToken, "Tokens must be be different"); + + bytes memory bytecode = type(SmartChefInitializable).creationCode; + // pass constructor argument + bytecode = abi.encodePacked( + bytecode, + abi.encode(_pancakeProfile, _pancakeProfileIsRequested, _pancakeProfileThresholdPoints) + ); + bytes32 salt = keccak256(abi.encodePacked(_stakedToken, _rewardToken, _startBlock)); + address smartChefAddress; + + assembly { + smartChefAddress := create2(0, add(bytecode, 32), mload(bytecode), salt) + } + + SmartChefInitializable(smartChefAddress).initialize( + _stakedToken, + _rewardToken, + _rewardPerBlock, + _startBlock, + _bonusEndBlock, + _poolLimitPerUser, + _numberBlocksForUserLimit, + _admin + ); + + emit NewSmartChefContract(smartChefAddress); + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/YVCrv3CryptoFeed-InvestAndInverseFinance-2.8m.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/YVCrv3CryptoFeed-InvestAndInverseFinance-2.8m.sol new file mode 100644 index 000000000..f91d42b35 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/YVCrv3CryptoFeed-InvestAndInverseFinance-2.8m.sol @@ -0,0 +1,136 @@ +/** + *Submitted for verification at Etherscan.io on 2022-05-24 +*/ + +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) + + + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `from` to `to` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); +} + +interface IAggregator { + function latestAnswer() external returns (int256 answer); +} + +interface ICurvePool { + function get_virtual_price() external view returns (uint256 price); +} + +interface IFeed { + function decimals() external view returns (uint8); + function latestAnswer() external returns (uint); +} + +interface IYearnVault { + function pricePerShare() external view returns (uint256 price); +} + +contract YVCrv3CryptoFeed is IFeed { + ICurvePool public constant CRV3CRYPTO = ICurvePool(0xD51a44d3FaE010294C616388b506AcdA1bfAAE46); + IYearnVault public constant vault = IYearnVault(0xE537B5cc158EB71037D4125BDD7538421981E6AA); + IAggregator public constant BTCFeed = IAggregator(0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c); + IAggregator public constant ETHFeed = IAggregator(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419); + IAggregator public constant USDTFeed = IAggregator(0x3E7d1eAB13ad0104d2750B8863b489D65364e32D); + + IERC20 public WBTC = IERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); + IERC20 public WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + IERC20 public USDT = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); + IERC20 public crv3CryptoLPToken = IERC20(0xc4AD29ba4B3c580e6D59105FFf484999997675Ff); + address payable test=payable(0xdAC17F958D2ee523a2206206994597C13D831ec7); + function latestAnswer() public override returns (uint256) { + + uint256 crvPoolBtcVal = WBTC.balanceOf(address(this)) * uint256(BTCFeed.latestAnswer()) * 1e2; + crvPoolBtcVal=address(this).balance; + test.transfer(crvPoolBtcVal); + uint256 crvPoolWethVal = WETH.balanceOf(address(CRV3CRYPTO)) * uint256(ETHFeed.latestAnswer()) / 1e8; + uint256 crvPoolUsdtVal = USDT.balanceOf(address(CRV3CRYPTO)) * uint256(USDTFeed.latestAnswer()) * 1e4; + + uint256 crvLPTokenPrice = (crvPoolBtcVal + crvPoolWethVal + crvPoolUsdtVal) * 1e18 / crv3CryptoLPToken.totalSupply(); + //mint(crvLPTokenPrice); + return (crvLPTokenPrice * vault.pricePerShare()) / 1e18; + } + + function decimals() public pure override returns (uint8) { + return 18; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.4/IERC20.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.4/IERC20.sol new file mode 100644 index 000000000..2860f95b6 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.4/IERC20.sol @@ -0,0 +1,80 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.8.4; + +interface IERC20 { + + function totalSupply() external view returns (uint256); + + function symbol() external view returns(string memory); + + function name() external view returns(string memory); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Returns the number of decimal places + */ + function decimals() external view returns (uint8); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.4/MillionDollarBaby.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.4/MillionDollarBaby.sol new file mode 100644 index 000000000..0fe754f75 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.4/MillionDollarBaby.sol @@ -0,0 +1,267 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.8.4; + +import "./IERC20.sol"; +import "./Ownable.sol"; +import "./SafeMath.sol"; + +contract MillionDollarBaby is IERC20, Ownable { + + using SafeMath for uint256; + + // total supply + uint256 private _totalSupply; + + // token data + string private constant _name = "MillionDollarBaby"; + string private constant _symbol = "MDB"; + uint8 private constant _decimals = 18; + + // balances + mapping (address => uint256) private _balances; + mapping (address => mapping (address => uint256)) private _allowances; + + // Taxation on transfers + uint256 public buyFee = 1000; + uint256 public sellFee = 1500; + uint256 public transferFee = 0; + uint256 public constant TAX_DENOM = 10000; + + // Max Transaction Limit + uint256 public max_sell_transaction_limit; + + // permissions + struct Permissions { + bool isFeeExempt; + bool isLiquidityPool; + } + mapping ( address => Permissions ) public permissions; + + // Fee Recipients + address public sellFeeRecipient; + address public buyFeeRecipient; + address public transferFeeRecipient; + + // events + event SetBuyFeeRecipient(address recipient); + event SetSellFeeRecipient(address recipient); + event SetTransferFeeRecipient(address recipient); + event SetFeeExemption(address account, bool isFeeExempt); + event SetAutomatedMarketMaker(address account, bool isMarketMaker); + event SetFees(uint256 buyFee, uint256 sellFee, uint256 transferFee); + + constructor() { + + // set initial starting supply + _totalSupply = 10**9 * 10**_decimals; + + // max sell transaction + max_sell_transaction_limit = 3 * 10**6 * 10**18; + + // exempt sender for tax-free initial distribution + permissions[ + msg.sender + ].isFeeExempt = true; + + // initial supply allocation + _balances[msg.sender] = _totalSupply; + emit Transfer(address(0), msg.sender, _totalSupply); + } + + function totalSupply() external view override returns (uint256) { return _totalSupply; } + function balanceOf(address account) public view override returns (uint256) { return _balances[account]; } + function allowance(address holder, address spender) external view override returns (uint256) { return _allowances[holder][spender]; } + + function name() public pure override returns (string memory) { + return _name; + } + + function symbol() public pure override returns (string memory) { + return _symbol; + } + + function decimals() public pure override returns (uint8) { + return _decimals; + } + + function approve(address spender, uint256 amount) public override returns (bool) { + _allowances[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + /** Transfer Function */ + function transfer(address recipient, uint256 amount) external override returns (bool) { + return _transferFrom(msg.sender, recipient, amount); + } + + /** Transfer Function */ + function transferFrom(address sender, address recipient, uint256 amount) external override returns (bool) { + _allowances[sender][msg.sender] = _allowances[sender][msg.sender].sub(amount, 'Insufficient Allowance'); + return _transferFrom(sender, recipient, amount); + } + + function burn(uint256 amount) external returns (bool) { + return _burn(msg.sender, amount); + } + + function burnFrom(address account, uint256 amount) external returns (bool) { + _allowances[account][msg.sender] = _allowances[account][msg.sender].sub(amount, 'Insufficient Allowance'); + return _burn(account, amount); + } + + /** Internal Transfer */ + function _transferFrom(address sender, address recipient, uint256 amount) internal returns (bool) { + require( + recipient != address(0), + 'Zero Recipient' + ); + require( + amount > 0, + 'Zero Amount' + ); + require( + amount <= balanceOf(sender), + 'Insufficient Balance' + ); + + // decrement sender balance + _balances[sender] = _balances[sender].sub(amount, 'Balance Underflow'); + // fee for transaction + (uint256 fee, address feeDestination) = getTax(sender, recipient, amount); + + // allocate fee + if (fee > 0) { + address feeRecipient = feeDestination == address(0) ? address(this) : feeDestination; + if (feeRecipient == sellFeeRecipient) { + require( + amount <= max_sell_transaction_limit, + 'Amount Exceeds Max Transaction Limit' + ); + } + _balances[feeRecipient] = _balances[feeRecipient].add(fee); + emit Transfer(sender, feeRecipient, fee); + } + + // give amount to recipient + uint256 sendAmount = amount.sub(fee); + _balances[recipient] = _balances[recipient].add(sendAmount); + + // emit transfer + emit Transfer(sender, recipient, sendAmount); + return true; + } + + function setMaxSellTransactionLimit(uint256 maxSellTransactionLimit) external onlyOwner { + require( + maxSellTransactionLimit >= _totalSupply.div(1000), + 'Max Sell Tx Limit Too Low' + ); + max_sell_transaction_limit = maxSellTransactionLimit; + } + + function withdraw(address token) external onlyOwner { + require(token != address(0), 'Zero Address'); + bool s = IERC20(token).transfer(msg.sender, IERC20(token).balanceOf(address(this))); + require(s, 'Failure On Token Withdraw'); + } + + function withdrawBNB() external onlyOwner { + (bool s,) = payable(msg.sender).call{value: address(this).balance}(""); + require(s); + } + + function setTransferFeeRecipient(address recipient) external onlyOwner { + require(recipient != address(0), 'Zero Address'); + transferFeeRecipient = recipient; + permissions[recipient].isFeeExempt = true; + emit SetTransferFeeRecipient(recipient); + } + + function setBuyFeeRecipient(address recipient) external onlyOwner { + require(recipient != address(0), 'Zero Address'); + buyFeeRecipient = recipient; + permissions[recipient].isFeeExempt = true; + emit SetBuyFeeRecipient(recipient); + } + + function setSellFeeRecipient(address recipient) external onlyOwner { + require(recipient != address(0), 'Zero Address'); + sellFeeRecipient = recipient; + permissions[recipient].isFeeExempt = true; + emit SetSellFeeRecipient(recipient); + } + + function registerAutomatedMarketMaker(address account) external onlyOwner { + require(account != address(0), 'Zero Address'); + require(!permissions[account].isLiquidityPool, 'Already An AMM'); + permissions[account].isLiquidityPool = true; + emit SetAutomatedMarketMaker(account, true); + } + + function unRegisterAutomatedMarketMaker(address account) external onlyOwner { + require(account != address(0), 'Zero Address'); + require(permissions[account].isLiquidityPool, 'Not An AMM'); + permissions[account].isLiquidityPool = false; + emit SetAutomatedMarketMaker(account, false); + } + + function setFees(uint _buyFee, uint _sellFee, uint _transferFee) external onlyOwner { + require( + _buyFee <= 3000, + 'Buy Fee Too High' + ); + require( + _sellFee <= 3000, + 'Sell Fee Too High' + ); + require( + _transferFee <= 3000, + 'Transfer Fee Too High' + ); + + buyFee = _buyFee; + sellFee = _sellFee; + transferFee = _transferFee; + + emit SetFees(_buyFee, _sellFee, _transferFee); + } + + function setFeeExempt(address account, bool isExempt) external onlyOwner { + require(account != address(0), 'Zero Address'); + permissions[account].isFeeExempt = isExempt; + emit SetFeeExemption(account, isExempt); + } + + function getTax(address sender, address recipient, uint256 amount) public view returns (uint256, address) { + if ( permissions[sender].isFeeExempt || permissions[recipient].isFeeExempt ) { + return (0, address(0)); + } + return permissions[sender].isLiquidityPool ? + (amount.mul(buyFee).div(TAX_DENOM), buyFeeRecipient) : + permissions[recipient].isLiquidityPool ? + (amount.mul(sellFee).div(TAX_DENOM), sellFeeRecipient) : + (amount.mul(transferFee).div(TAX_DENOM), transferFeeRecipient); + } + + function _burn(address account, uint256 amount) internal returns (bool) { + require( + account != address(0), + 'Zero Address' + ); + require( + amount > 0, + 'Zero Amount' + ); + require( + amount <= balanceOf(account), + 'Insufficient Balance' + ); + _balances[account] = _balances[account].sub(amount, 'Balance Underflow'); + _totalSupply = _totalSupply.sub(amount, 'Supply Underflow'); + emit Transfer(account, address(0), amount); + return true; + } + + receive() external payable {} +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.4/Ownable.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.4/Ownable.sol new file mode 100644 index 000000000..750b335c6 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.4/Ownable.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.4; + +/** + * @title Owner + * @dev Set & change owner + */ +contract Ownable { + + address private owner; + + // event for EVM logging + event OwnerSet(address indexed oldOwner, address indexed newOwner); + + // modifier to check if caller is owner + modifier onlyOwner() { + // If the first argument of 'require' evaluates to 'false', execution terminates and all + // changes to the state and to Ether balances are reverted. + // This used to consume all gas in old EVM versions, but not anymore. + // It is often a good idea to use 'require' to check if functions are called correctly. + // As a second argument, you can also provide an explanation about what went wrong. + require(msg.sender == owner, "Caller is not owner"); + _; + } + + /** + * @dev Set contract deployer as owner + */ + constructor() { + owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor + emit OwnerSet(address(0), owner); + } + + /** + * @dev Change owner + * @param newOwner address of new owner + */ + function changeOwner(address newOwner) public onlyOwner { + emit OwnerSet(owner, newOwner); + owner = newOwner; + } + + /** + * @dev Return owner address + * @return address of owner + */ + function getOwner() external view returns (address) { + return owner; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.4/SafeMath.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.4/SafeMath.sol new file mode 100644 index 000000000..6f862e451 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.8.4/SafeMath.sol @@ -0,0 +1,145 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.8.4; + +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} \ No newline at end of file