From 324dfe76fe0bbd6ae29a652f466a6547df39db5d Mon Sep 17 00:00:00 2001 From: xueyue Date: Fri, 29 Mar 2024 13:51:46 +0800 Subject: [PATCH 1/5] udpate detectors --- slither/detectors/all_detectors.py | 16 + slither/detectors/defi/__init__.py | 0 slither/detectors/defi/defi_action_nested.py | 343 +++ slither/detectors/defi/k_value_error.py | 81 + .../detectors/defi/price_manipulation_high.py | 255 ++ .../detectors/defi/price_manipulation_info.py | 209 ++ .../detectors/defi/price_manipulation_low.py | 257 ++ .../defi/price_manipulation_medium.py | 260 ++ .../defi/price_manipulation_tools.py | 75 + .../detectors/functions/centralized_info.py | 81 + .../functions/centralized_init_supply.py | 88 + .../detectors/functions/centralized_low.py | 80 + .../detectors/functions/centralized_medium.py | 78 + .../detectors/functions/centralized_other.py | 76 + .../detectors/functions/centralized_utils.py | 317 ++ slither/detectors/functions/modifier_utils.py | 80 + .../transaction_order_dependency_high.py | 100 + .../transaction_order_dependency_low.py | 82 + .../transaction_order_dependency_medium.py | 104 + slither/utils/function_dependency_tree.py | 220 ++ slither/utils/function_permission_check.py | 64 + .../centralized-init-supply/0.8.6/test.sol | 23 + .../0.4.24/Hundred.sol | 1029 +++++++ .../0.5.10/smartypay.sol | 223 ++ ...nnyPriceCalculatorBSC-pancakeBunny-45m.sol | 926 ++++++ .../centralized-risk-medium/0.6.12/VOLT.sol | 897 ++++++ .../centralized-risk-medium/0.7.4/ghost.sol | 643 ++++ .../centralized-risk-medium/0.7.6/vvisor.sol | 46 + .../centralized-risk-medium/0.8.19/test.sol | 68 + .../0.6.11/CommunityFund.sol | 1651 ++++++++++ .../0.6.11/StakeContract.sol | 487 +++ .../0.6.11/StrategyEllipsisImpl.sol | 1276 ++++++++ .../defi-action-nested/0.8.0/ArrayFinance.sol | 1124 +++++++ .../defi-action-nested/0.8.0/WUSDMaster.sol | 744 +++++ .../defi-action-nested/0.8.2/JAY.sol | 1343 ++++++++ .../0.8.2/Keep3rV2Oracle.sol | 367 +++ .../0.8.2/PancakeOracle.sol | 443 +++ .../0.8.2/SmartChefFactory.sol | 1064 +++++++ .../0.8.2/defi_price_manipulation.sol | 136 + .../0.5.10/bnbcorp.sol | 364 +++ .../price-manipulation-high/0.5.12/Issuer.sol | 2730 +++++++++++++++++ .../0.5.12/yDAI-EVENT-Value DeFi-21m.sol | 744 +++++ .../price-manipulation-high/0.6.11/BConst.sol | 62 + .../price-manipulation-high/0.6.11/BMath.sol | 252 ++ .../price-manipulation-high/0.6.11/BNum.sol | 137 + .../price-manipulation-high/0.6.11/BToken.sol | 192 ++ .../0.6.11/BeefyVault.sol | 941 ++++++ .../0.6.11/ICompLikeToken.sol | 7 + .../0.6.11/IIndexPool.sol | 202 ++ .../0.6.11/IndexPool.sol | 1368 +++++++++ ...nnyPriceCalculatorBSC-pancakeBunny-45m.sol | 926 ++++++ .../0.6.11/SafeMath.sol | 159 + .../0.6.11/Safemoon.sol | 1166 +++++++ .../0.6.11/SpaceGoat.sol | 1198 ++++++++ .../StrategyEllipsisImpl-BeltFinance-6m.sol | 1272 ++++++++ .../0.8.0/ArrayFinance-unknown.sol | 1124 +++++++ .../price-manipulation-high/0.8.0/Context.sol | 24 + .../0.8.0/EnumerableSet.sol | 357 +++ .../price-manipulation-high/0.8.0/GDS.sol | 899 ++++++ .../price-manipulation-high/0.8.0/IERC20.sol | 82 + .../0.8.0/IERC20Metadata.sol | 28 + .../0.8.0/IUniswapV2Factory.sol | 32 + .../0.8.0/IUniswapV2Pair.sol | 111 + .../0.8.0/IUniswapV2Router.sol | 208 ++ .../price-manipulation-high/0.8.0/Ownable.sol | 76 + .../0.8.0/beeminer.sol | 849 +++++ .../price-manipulation-high/0.8.0/token.sol | 98 + .../0.8.10/fasterBNB.sol | 1364 ++++++++ .../0.8.12/Oracle-DeusFinance-13m.sol | 31 + .../price-manipulation-high/0.8.2/JAY.sol | 1343 ++++++++ .../0.8.2/Keep3rV2Oracle.sol | 367 +++ .../PancakeOracle-PloutozFinance-365k.sol | 443 +++ .../0.8.2/SmartChefFactory.sol | 1064 +++++++ ...ryptoFeed-InvestAndInverseFinance-2.8m.sol | 136 + .../price-manipulation-info/0.8.2/Vault.sol | 204 ++ .../access/OwnableUpgradeable.sol | 95 + .../proxy/utils/Initializable.sol | 138 + .../security/ReentrancyGuardUpgradeable.sol | 75 + .../token/ERC20/ERC20Upgradeable.sol | 395 +++ .../token/ERC20/IERC20Upgradeable.sol | 82 + .../extensions/IERC20MetadataUpgradeable.sol | 28 + .../draft-IERC20PermitUpgradeable.sol | 60 + .../ERC20/utils/SafeERC20Upgradeable.sol | 116 + .../utils/AddressUpgradeable.sol | 195 ++ .../utils/ContextUpgradeable.sol | 37 + .../contracts/utils/math/SafeMath.sol | 227 ++ .../contracts/interfaces/IController.sol | 10 + .../efvault/contracts/interfaces/IVault.sol | 8 + .../contracts/utils/TransferHelper.sol | 58 + .../price-manipulation-info/0.8.2/test1.sol | 13 + .../0.6.11/BConst.sol | 62 + .../0.6.11/BMath.sol | 252 ++ .../price-manipulation-medium/0.6.11/BNum.sol | 137 + .../0.6.11/BToken.sol | 192 ++ .../0.6.11/BeefyVault.sol | 941 ++++++ .../0.6.11/ICompLikeToken.sol | 7 + .../0.6.11/IIndexPool.sol | 202 ++ .../0.6.11/IndexPool.sol | 1368 +++++++++ ...nnyPriceCalculatorBSC-pancakeBunny-45m.sol | 926 ++++++ .../0.6.11/SafeMath.sol | 159 + .../0.6.11/Safemoon.sol | 1166 +++++++ .../0.6.11/SpaceGoat.sol | 1198 ++++++++ .../StrategyEllipsisImpl-BeltFinance-6m.sol | 1272 ++++++++ .../0.8.12/Oracle-DeusFinance-13m.sol | 31 + .../price-manipulation-medium/0.8.2/JAY.sol | 1343 ++++++++ .../0.8.2/Keep3rV2Oracle.sol | 367 +++ .../PancakeOracle-PloutozFinance-365k.sol | 443 +++ .../0.8.2/SmartChefFactory.sol | 1064 +++++++ ...ryptoFeed-InvestAndInverseFinance-2.8m.sol | 136 + .../0.8.4/IERC20.sol | 80 + .../0.8.4/MillionDollarBaby.sol | 267 ++ .../0.8.4/Ownable.sol | 51 + .../0.8.4/SafeMath.sol | 145 + 113 files changed, 49262 insertions(+) create mode 100644 slither/detectors/defi/__init__.py create mode 100644 slither/detectors/defi/defi_action_nested.py create mode 100644 slither/detectors/defi/k_value_error.py create mode 100644 slither/detectors/defi/price_manipulation_high.py create mode 100644 slither/detectors/defi/price_manipulation_info.py create mode 100644 slither/detectors/defi/price_manipulation_low.py create mode 100644 slither/detectors/defi/price_manipulation_medium.py create mode 100644 slither/detectors/defi/price_manipulation_tools.py create mode 100644 slither/detectors/functions/centralized_info.py create mode 100644 slither/detectors/functions/centralized_init_supply.py create mode 100644 slither/detectors/functions/centralized_low.py create mode 100644 slither/detectors/functions/centralized_medium.py create mode 100644 slither/detectors/functions/centralized_other.py create mode 100644 slither/detectors/functions/centralized_utils.py create mode 100644 slither/detectors/functions/modifier_utils.py create mode 100644 slither/detectors/functions/transaction_order_dependency_high.py create mode 100644 slither/detectors/functions/transaction_order_dependency_low.py create mode 100644 slither/detectors/functions/transaction_order_dependency_medium.py create mode 100644 slither/utils/function_dependency_tree.py create mode 100644 slither/utils/function_permission_check.py create mode 100644 tests/e2e/detectors/test_data/centralized-init-supply/0.8.6/test.sol create mode 100644 tests/e2e/detectors/test_data/centralized-risk-medium/0.4.24/Hundred.sol create mode 100644 tests/e2e/detectors/test_data/centralized-risk-medium/0.5.10/smartypay.sol create mode 100644 tests/e2e/detectors/test_data/centralized-risk-medium/0.6.11/PancakeBunnyPriceCalculatorBSC-pancakeBunny-45m.sol create mode 100644 tests/e2e/detectors/test_data/centralized-risk-medium/0.6.12/VOLT.sol create mode 100644 tests/e2e/detectors/test_data/centralized-risk-medium/0.7.4/ghost.sol create mode 100644 tests/e2e/detectors/test_data/centralized-risk-medium/0.7.6/vvisor.sol create mode 100644 tests/e2e/detectors/test_data/centralized-risk-medium/0.8.19/test.sol create mode 100644 tests/e2e/detectors/test_data/defi-action-nested/0.6.11/CommunityFund.sol create mode 100644 tests/e2e/detectors/test_data/defi-action-nested/0.6.11/StakeContract.sol create mode 100644 tests/e2e/detectors/test_data/defi-action-nested/0.6.11/StrategyEllipsisImpl.sol create mode 100644 tests/e2e/detectors/test_data/defi-action-nested/0.8.0/ArrayFinance.sol create mode 100644 tests/e2e/detectors/test_data/defi-action-nested/0.8.0/WUSDMaster.sol create mode 100644 tests/e2e/detectors/test_data/defi-action-nested/0.8.2/JAY.sol create mode 100644 tests/e2e/detectors/test_data/defi-action-nested/0.8.2/Keep3rV2Oracle.sol create mode 100644 tests/e2e/detectors/test_data/defi-action-nested/0.8.2/PancakeOracle.sol create mode 100644 tests/e2e/detectors/test_data/defi-action-nested/0.8.2/SmartChefFactory.sol create mode 100644 tests/e2e/detectors/test_data/defi-action-nested/0.8.2/defi_price_manipulation.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.5.10/bnbcorp.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.5.12/Issuer.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.5.12/yDAI-EVENT-Value DeFi-21m.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BConst.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BMath.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BNum.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BToken.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BeefyVault.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/ICompLikeToken.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/IIndexPool.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/IndexPool.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/PancakeBunnyPriceCalculatorBSC-pancakeBunny-45m.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/SafeMath.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/Safemoon.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/SpaceGoat.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/StrategyEllipsisImpl-BeltFinance-6m.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/ArrayFinance-unknown.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/Context.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/EnumerableSet.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/GDS.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IERC20.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IERC20Metadata.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IUniswapV2Factory.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IUniswapV2Pair.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/IUniswapV2Router.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/Ownable.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/beeminer.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.0/token.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.10/fasterBNB.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.12/Oracle-DeusFinance-13m.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/JAY.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/Keep3rV2Oracle.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/PancakeOracle-PloutozFinance-365k.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/SmartChefFactory.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.8.2/YVCrv3CryptoFeed-InvestAndInverseFinance-2.8m.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/Vault.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts/utils/math/SafeMath.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/interfaces/IController.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/interfaces/IVault.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/utils/TransferHelper.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/test1.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BConst.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BMath.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BNum.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BToken.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BeefyVault.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/ICompLikeToken.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/IIndexPool.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/IndexPool.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/PancakeBunnyPriceCalculatorBSC-pancakeBunny-45m.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/SafeMath.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/Safemoon.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/SpaceGoat.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/StrategyEllipsisImpl-BeltFinance-6m.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.8.12/Oracle-DeusFinance-13m.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/JAY.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/Keep3rV2Oracle.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/PancakeOracle-PloutozFinance-365k.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/SmartChefFactory.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.8.2/YVCrv3CryptoFeed-InvestAndInverseFinance-2.8m.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.8.4/IERC20.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.8.4/MillionDollarBaby.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.8.4/Ownable.sol create mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.8.4/SafeMath.sol diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 4151759f0d..110339aeb9 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 0000000000..e69de29bb2 diff --git a/slither/detectors/defi/defi_action_nested.py b/slither/detectors/defi/defi_action_nested.py new file mode 100644 index 0000000000..9fc8e97818 --- /dev/null +++ b/slither/detectors/defi/defi_action_nested.py @@ -0,0 +1,343 @@ +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 = "Arbitrary `from` in transferFrom used with permit" + WIKI_DESCRIPTION = ( + "Detect when `msg.sender` is not used as `from` in transferFrom and permit is used." + ) + WIKI_EXPLOIT_SCENARIO = """ +```solidity + function bad(address from, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s, address to) public { + erc20.permit(from, address(this), value, deadline, v, r, s); + erc20.transferFrom(from, to, value); + } +``` +If an ERC20 token does not implement permit and has a fallback function e.g. WETH, transferFrom allows an attacker to transfer all tokens approved for this contract.""" + + WIKI_RECOMMENDATION = """ +Ensure that the underlying ERC20 token correctly implements a permit function. +""" + 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 + + # 检查一个call是否是erc20向用户转账的call,返回是/否以及call本身 + 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"]: + # 第一个转账参数是msg.sender或外部传入的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 + + # 检查一个node中是否有mapping操作,是否是一个对msg.sender或者input参数进行赋值的,返回是/否,以及对应所具体赋值的变量 + 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) + + # 质押或存入的特点:有向内转账,有使用msgsender或者是传入参数的用户数组操作,有数据依赖 + 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: + # 检查是否存在向用户转账的ERC20转账函数 + 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 + # 是否有数组操作,并且参数是msg.sender或者func参数中的address类型参数 + 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 + # 数组操作和转账之间存在数据依赖 + if self._check_two_arguments_if_dependent(call_arguments,var_read,func): + dependencyScore=self.defaultDependencyScore + + return diffscore,transferMatchScore,assignementToUserScore,dependencyScore + + # 提取和取消质押的特点:有向外转账,有使用msgsender或者是传入参数的用户数组操作,有数据依赖 + 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: + # 检查是否存在向用户转账的ERC20转账函数 + 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 + # 是否有数组操作,并且参数是msg.sender或者func参数中的address类型参数 + 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 + # 数组操作和转账之间存在数据依赖 + if self._check_two_arguments_if_dependent(call_arguments,var_read,func): + dependencyScore=self.defaultDependencyScore + return diffscore,transferMatchScore,assignementToUserScore,dependencyScore + + # lending特点,有向外转账,有基于用户的mapping操作 + 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: + # 检查是否存在向用户转账的ERC20转账函数 + 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 + # 是否有数组操作,并且参数是msg.sender或者func参数中的address类型参数 + 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 + # 数组操作和转账之间存在数据依赖 + if self._check_two_arguments_if_dependent(call_arguments,var_read,func): + dependencyScore=self.defaultDependencyScore + return diffscore,transferMatchScore,assignementToUserScore,dependencyScore + + # liquidate的特点,有向外转账,有向内转账,有mapping操作 + 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: + # 检查是否存在向用户转账的ERC20转账函数 + 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 + + # 是否有数组操作,并且参数是msg.sender或者func参数中的address类型参数 + 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 + # 数组操作和转账之间存在数据依赖 + 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 it's father defi action which have 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 0000000000..e0873f7c97 --- /dev/null +++ b/slither/detectors/defi/k_value_error.py @@ -0,0 +1,81 @@ +import re +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification + + +class KValueError(AbstractDetector): + """ + Module detecting multiple constructors in the same contract. + (This was possible prior to Solidity 0.4.23, using old and new constructor schemes). + """ + + ARGUMENT = "k-value-error" + HELP = "Multiple constructor schemes" + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.HIGH + + WIKI = " " + + WIKI_TITLE = "Multiple constructor schemes" + WIKI_DESCRIPTION = ( + "Detect multiple constructor definitions in the same contract (using new and old schemes)." + ) + + # region wiki_exploit_scenario + WIKI_EXPLOIT_SCENARIO = """ +```solidity +contract A { + uint x; + constructor() public { + x = 0; + } + function A() public { + x = 1; + } + + function test() public returns(uint) { + return x; + } +} +``` +In Solidity [0.4.22](https://github.com/ethereum/solidity/releases/tag/v0.4.23), a contract with both constructor schemes will compile. The first constructor will take precedence over the second, which may be unintended.""" + # endregion wiki_exploit_scenario + + WIKI_RECOMMENDATION = "Only declare one constructor, preferably using the new scheme `constructor(...)` instead of `function (...)`." + + 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: + # 判断是否有可能是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 0000000000..11cf2daecc --- /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 0000000000..1158e96d5a --- /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 0000000000..0d71a161bf --- /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 0000000000..b7940a3bc1 --- /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 0000000000..4f24ce54e1 --- /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 0000000000..4ef83d6cde --- /dev/null +++ b/slither/detectors/functions/centralized_info.py @@ -0,0 +1,81 @@ +""" +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 modifiers that return a default value + """ + + ARGUMENT = "centralized-risk-informational" + HELP = "Modifiers that can return the default value" + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH + WIKI = " " + + WIKI_TITLE = "Centralized Risk" + WIKI_DESCRIPTION = "aaa" + + # region wiki_exploit_scenario + WIKI_EXPLOIT_SCENARIO = """ +If the condition in `myModif` is false, the execution of `get()` will return 0.""" + # endregion wiki_exploit_scenario + + WIKI_RECOMMENDATION = "All the paths in a modifier must execute `_` or revert." + + 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 0000000000..d24318d172 --- /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 0000000000..1747330b1a --- /dev/null +++ b/slither/detectors/functions/centralized_low.py @@ -0,0 +1,80 @@ +""" +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 modifiers that return a default value + """ + + ARGUMENT = "centralized-risk-low" + HELP = "Modifiers that can return the default value" + IMPACT = DetectorClassification.LOW + CONFIDENCE = DetectorClassification.HIGH + WIKI = " " + + WIKI_TITLE = "Centralized Risk" + WIKI_DESCRIPTION = "aaa" + + # region wiki_exploit_scenario + WIKI_EXPLOIT_SCENARIO = """ +If the condition in `myModif` is false, the execution of `get()` will return 0.""" + # endregion wiki_exploit_scenario + + WIKI_RECOMMENDATION = "All the paths in a modifier must execute `_` or revert." + + 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 0000000000..02d3c4ff13 --- /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" + WIKI_DESCRIPTION = "aaa" + + # region wiki_exploit_scenario + WIKI_EXPLOIT_SCENARIO = """ +If the condition in `myModif` is false, the execution of `get()` will return 0.""" + # endregion wiki_exploit_scenario + + WIKI_RECOMMENDATION = "All the paths in a modifier must execute `_` or revert." + + 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 0000000000..19ab487697 --- /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" + WIKI_DESCRIPTION = "aaa" + + # region wiki_exploit_scenario + WIKI_EXPLOIT_SCENARIO = """ +If the condition in `myModif` is false, the execution of `get()` will return 0.""" + # endregion wiki_exploit_scenario + + WIKI_RECOMMENDATION = "All the paths in a modifier must execute `_` or revert." + + 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 0000000000..7f3b3b801a --- /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 0000000000..211212f1b4 --- /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: + """ + 是否是防重入锁 + 1、有对状态变量的读写操作 + 2、有状态变量condition语句(require、if) + 3、有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: + """ + 是否有权限控制(onlyXXX) + 1、有placeholder:"_" + 2、有状态变量的读写操作 + 3、有包含了msg.sender的call行为语句(require、if) + """ + + # 不存在placeholder + if not any(node.type.name == 'PLACEHOLDER' for node in modifier.nodes): + return False + # 有状态变量的读写操作 + 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 0000000000..902bd5c3e9 --- /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 0000000000..96f316b54c --- /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 0000000000..8e8acc69d4 --- /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 0000000000..e762f82766 --- /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 0000000000..1360f8f599 --- /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 0000000000..90479603a6 --- /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 0000000000..0ba0e4c118 --- /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 0000000000..92e7b18d39 --- /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 0000000000..3b091f62fb --- /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 0000000000..88afd9171e --- /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 0000000000..ecf114190e --- /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 0000000000..f180ef0e3e --- /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 0000000000..6dc14a2583 --- /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 0000000000..6d1dd174b4 --- /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 0000000000..1f1f9857e2 --- /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 0000000000..e1682b1bf5 --- /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 0000000000..c47b78800b --- /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 0000000000..7357acc535 --- /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 0000000000..83d5de8744 --- /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 0000000000..9d0f9d8ad6 --- /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 0000000000..bf84cf99b1 --- /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 0000000000..22d5f2a6e4 --- /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 0000000000..f91d42b357 --- /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 0000000000..80c8a9a0ac --- /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/Issuer.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.5.12/Issuer.sol new file mode 100644 index 0000000000..7fa8525109 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.5.12/Issuer.sol @@ -0,0 +1,2730 @@ +/** + *Submitted for verification at BscScan.com on 2021-08-02 +*/ + +/* + ___ _ ___ _ + | .\ ___ _ _ <_> ___ | __><_>._ _ ___ ._ _ ___ ___ + | _// ._>| '_>| ||___|| _> | || ' |<_> || ' |/ | '/ ._> + |_| \___.|_| |_| |_| |_||_|_|<___||_|_|\_|_.\___. + +* PeriFinance: Issuer.sol +* +* Latest source (may be newer): https://github.com/perifinance/peri-finance/blob/master/contracts/Issuer.sol +* Docs: Will be added in the future. +* https://docs.peri.finance/contracts/source/contracts/Issuer +* +* Contract Dependencies: +* - IAddressResolver +* - IIssuer +* - MixinResolver +* - MixinSystemSettings +* - Owned +* Libraries: +* - SafeDecimalMath +* - SafeMath +* +* MIT License +* =========== +* +* Copyright (c) 2021 PeriFinance +* +* 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 +*/ + + + +pragma solidity ^0.5.0; + +// https://docs.peri.finance/contracts/source/contracts/owned +contract Owned { + address public owner; + address public nominatedOwner; + + constructor(address _owner) public { + require(_owner != address(0), "Owner address cannot be 0"); + owner = _owner; + emit OwnerChanged(address(0), _owner); + } + + function nominateNewOwner(address _owner) external onlyOwner { + nominatedOwner = _owner; + emit OwnerNominated(_owner); + } + + function acceptOwnership() external { + require(msg.sender == nominatedOwner, "You must be nominated before you can accept ownership"); + emit OwnerChanged(owner, nominatedOwner); + owner = nominatedOwner; + nominatedOwner = address(0); + } + + modifier onlyOwner { + _onlyOwner(); + _; + } + + function _onlyOwner() private view { + require(msg.sender == owner, "Only the contract owner may perform this action"); + } + + event OwnerNominated(address newOwner); + event OwnerChanged(address oldOwner, address newOwner); +} + + +// https://docs.peri.finance/contracts/source/interfaces/iaddressresolver +interface IAddressResolver { + function getAddress(bytes32 name) external view returns (address); + + function getPynth(bytes32 key) external view returns (address); + + function requireAndGetAddress(bytes32 name, string calldata reason) external view returns (address); +} + + +// https://docs.peri.finance/contracts/source/interfaces/ipynth +interface IPynth { + // Views + function currencyKey() external view returns (bytes32); + + function transferablePynths(address account) external view returns (uint); + + // Mutative functions + function transferAndSettle(address to, uint value) external returns (bool); + + function transferFromAndSettle( + address from, + address to, + uint value + ) external returns (bool); + + // Restricted: used internally to PeriFinance + function burn(address account, uint amount) external; + + function issue(address account, uint amount) external; +} + + +// https://docs.peri.finance/contracts/source/interfaces/iissuer +interface IIssuer { + // Views + function anyPynthOrPERIRateIsInvalid() external view returns (bool anyRateInvalid); + + function availableCurrencyKeys() external view returns (bytes32[] memory); + + function availablePynthCount() external view returns (uint); + + function availablePynths(uint index) external view returns (IPynth); + + function canBurnPynths(address account) external view returns (bool); + + function collateral(address account) external view returns (uint); + + function collateralisationRatio(address issuer) external view returns (uint); + + function collateralisationRatioAndAnyRatesInvalid(address _issuer) + external + view + returns (uint cratio, bool anyRateIsInvalid); + + function debtBalanceOf(address issuer, bytes32 currencyKey) external view returns (uint debtBalance); + + function issuanceRatio() external view returns (uint); + + function externalTokenLimit() external view returns (uint); + + function lastIssueEvent(address account) external view returns (uint); + + function maxIssuablePynths(address issuer) external view returns (uint maxIssuable); + + function externalTokenQuota( + address _account, + uint _addtionalpUSD, + uint _addtionalExToken, + bool _isIssue + ) external view returns (uint); + + function maxExternalTokenStakeAmount(address _account, bytes32 _currencyKey) + external + view + returns (uint issueAmountToQuota, uint stakeAmountToQuota); + + function minimumStakeTime() external view returns (uint); + + function remainingIssuablePynths(address issuer) + external + view + returns ( + uint maxIssuable, + uint alreadyIssued, + uint totalSystemDebt + ); + + function pynths(bytes32 currencyKey) external view returns (IPynth); + + function getPynths(bytes32[] calldata currencyKeys) external view returns (IPynth[] memory); + + function pynthsByAddress(address pynthAddress) external view returns (bytes32); + + function totalIssuedPynths(bytes32 currencyKey, bool excludeEtherCollateral) external view returns (uint); + + function transferablePeriFinanceAndAnyRateIsInvalid(address account, uint balance) + external + view + returns (uint transferable, bool anyRateIsInvalid); + + // Restricted: used internally to PeriFinance + function issuePynths( + address _issuer, + bytes32 _currencyKey, + uint _issueAmount + ) external; + + function issueMaxPynths(address _issuer) external; + + function issuePynthsToMaxQuota(address _issuer, bytes32 _currencyKey) external; + + function burnPynths( + address _from, + bytes32 _currencyKey, + uint _burnAmount + ) external; + + function fitToClaimable(address _from) external; + + function exit(address _from) external; + + function liquidateDelinquentAccount( + address account, + uint pusdAmount, + address liquidator + ) external returns (uint totalRedeemed, uint amountToLiquidate); +} + + +// Inheritance + + +// Internal references + + +// https://docs.peri.finance/contracts/source/contracts/addressresolver +contract AddressResolver is Owned, IAddressResolver { + mapping(bytes32 => address) public repository; + + constructor(address _owner) public Owned(_owner) {} + + /* ========== RESTRICTED FUNCTIONS ========== */ + + function importAddresses(bytes32[] calldata names, address[] calldata destinations) external onlyOwner { + require(names.length == destinations.length, "Input lengths must match"); + + for (uint i = 0; i < names.length; i++) { + bytes32 name = names[i]; + address destination = destinations[i]; + repository[name] = destination; + emit AddressImported(name, destination); + } + } + + /* ========= PUBLIC FUNCTIONS ========== */ + + function rebuildCaches(MixinResolver[] calldata destinations) external { + for (uint i = 0; i < destinations.length; i++) { + destinations[i].rebuildCache(); + } + } + + /* ========== VIEWS ========== */ + + function areAddressesImported(bytes32[] calldata names, address[] calldata destinations) external view returns (bool) { + for (uint i = 0; i < names.length; i++) { + if (repository[names[i]] != destinations[i]) { + return false; + } + } + return true; + } + + function getAddress(bytes32 name) external view returns (address) { + return repository[name]; + } + + function requireAndGetAddress(bytes32 name, string calldata reason) external view returns (address) { + address _foundAddress = repository[name]; + require(_foundAddress != address(0), reason); + return _foundAddress; + } + + function getPynth(bytes32 key) external view returns (address) { + IIssuer issuer = IIssuer(repository["Issuer"]); + require(address(issuer) != address(0), "Cannot find Issuer address"); + return address(issuer.pynths(key)); + } + + /* ========== EVENTS ========== */ + + event AddressImported(bytes32 name, address destination); +} + + +// solhint-disable payable-fallback + +// https://docs.peri.finance/contracts/source/contracts/readproxy +contract ReadProxy is Owned { + address public target; + + constructor(address _owner) public Owned(_owner) {} + + function setTarget(address _target) external onlyOwner { + target = _target; + emit TargetUpdated(target); + } + + function() external { + // The basics of a proxy read call + // Note that msg.sender in the underlying will always be the address of this contract. + assembly { + calldatacopy(0, 0, calldatasize) + + // Use of staticcall - this will revert if the underlying function mutates state + let result := staticcall(gas, sload(target_slot), 0, calldatasize, 0, 0) + returndatacopy(0, 0, returndatasize) + + if iszero(result) { + revert(0, returndatasize) + } + return(0, returndatasize) + } + } + + event TargetUpdated(address newTarget); +} + + +// Inheritance + + +// Internal references + + +// https://docs.peri.finance/contracts/source/contracts/mixinresolver +contract MixinResolver { + AddressResolver public resolver; + + mapping(bytes32 => address) private addressCache; + + constructor(address _resolver) internal { + resolver = AddressResolver(_resolver); + } + + /* ========== INTERNAL FUNCTIONS ========== */ + + function combineArrays(bytes32[] memory first, bytes32[] memory second) + internal + pure + returns (bytes32[] memory combination) + { + combination = new bytes32[](first.length + second.length); + + for (uint i = 0; i < first.length; i++) { + combination[i] = first[i]; + } + + for (uint j = 0; j < second.length; j++) { + combination[first.length + j] = second[j]; + } + } + + /* ========== PUBLIC FUNCTIONS ========== */ + + // Note: this function is public not external in order for it to be overridden and invoked via super in subclasses + function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {} + + function rebuildCache() public { + bytes32[] memory requiredAddresses = resolverAddressesRequired(); + // The resolver must call this function whenver it updates its state + for (uint i = 0; i < requiredAddresses.length; i++) { + bytes32 name = requiredAddresses[i]; + // Note: can only be invoked once the resolver has all the targets needed added + address destination = + resolver.requireAndGetAddress(name, string(abi.encodePacked("Resolver missing target: ", name))); + addressCache[name] = destination; + emit CacheUpdated(name, destination); + } + } + + /* ========== VIEWS ========== */ + + function isResolverCached() external view returns (bool) { + bytes32[] memory requiredAddresses = resolverAddressesRequired(); + for (uint i = 0; i < requiredAddresses.length; i++) { + bytes32 name = requiredAddresses[i]; + // false if our cache is invalid or if the resolver doesn't have the required address + if (resolver.getAddress(name) != addressCache[name] || addressCache[name] == address(0)) { + return false; + } + } + + return true; + } + + /* ========== INTERNAL FUNCTIONS ========== */ + + function requireAndGetAddress(bytes32 name) internal view returns (address) { + address _foundAddress = addressCache[name]; + require(_foundAddress != address(0), string(abi.encodePacked("Missing address: ", name))); + return _foundAddress; + } + + /* ========== EVENTS ========== */ + + event CacheUpdated(bytes32 name, address destination); +} + + +// https://docs.peri.finance/contracts/source/interfaces/iflexiblestorage +interface IFlexibleStorage { + // Views + function getUIntValue(bytes32 contractName, bytes32 record) external view returns (uint); + + function getUIntValues(bytes32 contractName, bytes32[] calldata records) external view returns (uint[] memory); + + function getIntValue(bytes32 contractName, bytes32 record) external view returns (int); + + function getIntValues(bytes32 contractName, bytes32[] calldata records) external view returns (int[] memory); + + function getAddressValue(bytes32 contractName, bytes32 record) external view returns (address); + + function getAddressValues(bytes32 contractName, bytes32[] calldata records) external view returns (address[] memory); + + function getBoolValue(bytes32 contractName, bytes32 record) external view returns (bool); + + function getBoolValues(bytes32 contractName, bytes32[] calldata records) external view returns (bool[] memory); + + function getBytes32Value(bytes32 contractName, bytes32 record) external view returns (bytes32); + + function getBytes32Values(bytes32 contractName, bytes32[] calldata records) external view returns (bytes32[] memory); + + // Mutative functions + function deleteUIntValue(bytes32 contractName, bytes32 record) external; + + function deleteIntValue(bytes32 contractName, bytes32 record) external; + + function deleteAddressValue(bytes32 contractName, bytes32 record) external; + + function deleteBoolValue(bytes32 contractName, bytes32 record) external; + + function deleteBytes32Value(bytes32 contractName, bytes32 record) external; + + function setUIntValue( + bytes32 contractName, + bytes32 record, + uint value + ) external; + + function setUIntValues( + bytes32 contractName, + bytes32[] calldata records, + uint[] calldata values + ) external; + + function setIntValue( + bytes32 contractName, + bytes32 record, + int value + ) external; + + function setIntValues( + bytes32 contractName, + bytes32[] calldata records, + int[] calldata values + ) external; + + function setAddressValue( + bytes32 contractName, + bytes32 record, + address value + ) external; + + function setAddressValues( + bytes32 contractName, + bytes32[] calldata records, + address[] calldata values + ) external; + + function setBoolValue( + bytes32 contractName, + bytes32 record, + bool value + ) external; + + function setBoolValues( + bytes32 contractName, + bytes32[] calldata records, + bool[] calldata values + ) external; + + function setBytes32Value( + bytes32 contractName, + bytes32 record, + bytes32 value + ) external; + + function setBytes32Values( + bytes32 contractName, + bytes32[] calldata records, + bytes32[] calldata values + ) external; +} + + +// Internal references + + +// https://docs.peri.finance/contracts/source/contracts/mixinsystemsettings +contract MixinSystemSettings is MixinResolver { + bytes32 internal constant SETTING_CONTRACT_NAME = "SystemSettings"; + + bytes32 internal constant SETTING_WAITING_PERIOD_SECS = "waitingPeriodSecs"; + bytes32 internal constant SETTING_PRICE_DEVIATION_THRESHOLD_FACTOR = "priceDeviationThresholdFactor"; + bytes32 internal constant SETTING_ISSUANCE_RATIO = "issuanceRatio"; + bytes32 internal constant SETTING_FEE_PERIOD_DURATION = "feePeriodDuration"; + bytes32 internal constant SETTING_TARGET_THRESHOLD = "targetThreshold"; + bytes32 internal constant SETTING_LIQUIDATION_DELAY = "liquidationDelay"; + bytes32 internal constant SETTING_LIQUIDATION_RATIO = "liquidationRatio"; + bytes32 internal constant SETTING_LIQUIDATION_PENALTY = "liquidationPenalty"; + bytes32 internal constant SETTING_RATE_STALE_PERIOD = "rateStalePeriod"; + bytes32 internal constant SETTING_EXCHANGE_FEE_RATE = "exchangeFeeRate"; + bytes32 internal constant SETTING_MINIMUM_STAKE_TIME = "minimumStakeTime"; + bytes32 internal constant SETTING_AGGREGATOR_WARNING_FLAGS = "aggregatorWarningFlags"; + bytes32 internal constant SETTING_TRADING_REWARDS_ENABLED = "tradingRewardsEnabled"; + bytes32 internal constant SETTING_DEBT_SNAPSHOT_STALE_TIME = "debtSnapshotStaleTime"; + bytes32 internal constant SETTING_CROSS_DOMAIN_DEPOSIT_GAS_LIMIT = "crossDomainDepositGasLimit"; + bytes32 internal constant SETTING_CROSS_DOMAIN_ESCROW_GAS_LIMIT = "crossDomainEscrowGasLimit"; + bytes32 internal constant SETTING_CROSS_DOMAIN_REWARD_GAS_LIMIT = "crossDomainRewardGasLimit"; + bytes32 internal constant SETTING_CROSS_DOMAIN_WITHDRAWAL_GAS_LIMIT = "crossDomainWithdrawalGasLimit"; + bytes32 internal constant SETTING_EXTERNAL_TOKEN_QUOTA = "externalTokenQuota"; + + bytes32 internal constant CONTRACT_FLEXIBLESTORAGE = "FlexibleStorage"; + + enum CrossDomainMessageGasLimits {Deposit, Escrow, Reward, Withdrawal} + + constructor(address _resolver) internal MixinResolver(_resolver) {} + + function resolverAddressesRequired() public view returns (bytes32[] memory addresses) { + addresses = new bytes32[](1); + addresses[0] = CONTRACT_FLEXIBLESTORAGE; + } + + function flexibleStorage() internal view returns (IFlexibleStorage) { + return IFlexibleStorage(requireAndGetAddress(CONTRACT_FLEXIBLESTORAGE)); + } + + function _getGasLimitSetting(CrossDomainMessageGasLimits gasLimitType) internal pure returns (bytes32) { + if (gasLimitType == CrossDomainMessageGasLimits.Deposit) { + return SETTING_CROSS_DOMAIN_DEPOSIT_GAS_LIMIT; + } else if (gasLimitType == CrossDomainMessageGasLimits.Escrow) { + return SETTING_CROSS_DOMAIN_ESCROW_GAS_LIMIT; + } else if (gasLimitType == CrossDomainMessageGasLimits.Reward) { + return SETTING_CROSS_DOMAIN_REWARD_GAS_LIMIT; + } else if (gasLimitType == CrossDomainMessageGasLimits.Withdrawal) { + return SETTING_CROSS_DOMAIN_WITHDRAWAL_GAS_LIMIT; + } else { + revert("Unknown gas limit type"); + } + } + + function getCrossDomainMessageGasLimit(CrossDomainMessageGasLimits gasLimitType) internal view returns (uint) { + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, _getGasLimitSetting(gasLimitType)); + } + + function getTradingRewardsEnabled() internal view returns (bool) { + return flexibleStorage().getBoolValue(SETTING_CONTRACT_NAME, SETTING_TRADING_REWARDS_ENABLED); + } + + function getWaitingPeriodSecs() internal view returns (uint) { + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_WAITING_PERIOD_SECS); + } + + function getPriceDeviationThresholdFactor() internal view returns (uint) { + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_PRICE_DEVIATION_THRESHOLD_FACTOR); + } + + function getIssuanceRatio() internal view returns (uint) { + // lookup on flexible storage directly for gas savings (rather than via SystemSettings) + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ISSUANCE_RATIO); + } + + function getFeePeriodDuration() internal view returns (uint) { + // lookup on flexible storage directly for gas savings (rather than via SystemSettings) + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_FEE_PERIOD_DURATION); + } + + function getTargetThreshold() internal view returns (uint) { + // lookup on flexible storage directly for gas savings (rather than via SystemSettings) + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_TARGET_THRESHOLD); + } + + function getLiquidationDelay() internal view returns (uint) { + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_DELAY); + } + + function getLiquidationRatio() internal view returns (uint) { + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_RATIO); + } + + function getLiquidationPenalty() internal view returns (uint) { + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_PENALTY); + } + + function getRateStalePeriod() internal view returns (uint) { + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_RATE_STALE_PERIOD); + } + + function getExchangeFeeRate(bytes32 currencyKey) internal view returns (uint) { + return + flexibleStorage().getUIntValue( + SETTING_CONTRACT_NAME, + keccak256(abi.encodePacked(SETTING_EXCHANGE_FEE_RATE, currencyKey)) + ); + } + + function getMinimumStakeTime() internal view returns (uint) { + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_MINIMUM_STAKE_TIME); + } + + function getAggregatorWarningFlags() internal view returns (address) { + return flexibleStorage().getAddressValue(SETTING_CONTRACT_NAME, SETTING_AGGREGATOR_WARNING_FLAGS); + } + + function getDebtSnapshotStaleTime() internal view returns (uint) { + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_DEBT_SNAPSHOT_STALE_TIME); + } + + function getExternalTokenQuota() internal view returns (uint) { + return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_EXTERNAL_TOKEN_QUOTA); + } +} + + +/** + * @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) { + require(b <= a, "SafeMath: subtraction overflow"); + 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-solidity/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) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0, "SafeMath: division by zero"); + 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) { + require(b != 0, "SafeMath: modulo by zero"); + return a % b; + } +} + + +// Libraries + + +// https://docs.peri.finance/contracts/source/libraries/safedecimalmath +library SafeDecimalMath { + using SafeMath for uint; + + /* Number of decimal places in the representations. */ + uint8 public constant decimals = 18; + uint8 public constant highPrecisionDecimals = 27; + + /* The number representing 1.0. */ + uint public constant UNIT = 10**uint(decimals); + + /* The number representing 1.0 for higher fidelity numbers. */ + uint public constant PRECISE_UNIT = 10**uint(highPrecisionDecimals); + uint private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 10**uint(highPrecisionDecimals - decimals); + + /** + * @return Provides an interface to UNIT. + */ + function unit() external pure returns (uint) { + return UNIT; + } + + /** + * @return Provides an interface to PRECISE_UNIT. + */ + function preciseUnit() external pure returns (uint) { + return PRECISE_UNIT; + } + + /** + * @return The result of multiplying x and y, interpreting the operands as fixed-point + * decimals. + * + * @dev A unit factor is divided out after the product of x and y is evaluated, + * so that product must be less than 2**256. As this is an integer division, + * the internal division always rounds down. This helps save on gas. Rounding + * is more expensive on gas. + */ + function multiplyDecimal(uint x, uint y) internal pure returns (uint) { + /* Divide by UNIT to remove the extra factor introduced by the product. */ + return x.mul(y) / UNIT; + } + + /** + * @return The result of safely multiplying x and y, interpreting the operands + * as fixed-point decimals of the specified precision unit. + * + * @dev The operands should be in the form of a the specified unit factor which will be + * divided out after the product of x and y is evaluated, so that product must be + * less than 2**256. + * + * Unlike multiplyDecimal, this function rounds the result to the nearest increment. + * Rounding is useful when you need to retain fidelity for small decimal numbers + * (eg. small fractions or percentages). + */ + function _multiplyDecimalRound( + uint x, + uint y, + uint precisionUnit + ) private pure returns (uint) { + /* Divide by UNIT to remove the extra factor introduced by the product. */ + uint quotientTimesTen = x.mul(y) / (precisionUnit / 10); + + if (quotientTimesTen % 10 >= 5) { + quotientTimesTen += 10; + } + + return quotientTimesTen / 10; + } + + /** + * @return The result of safely multiplying x and y, interpreting the operands + * as fixed-point decimals of a precise unit. + * + * @dev The operands should be in the precise unit factor which will be + * divided out after the product of x and y is evaluated, so that product must be + * less than 2**256. + * + * Unlike multiplyDecimal, this function rounds the result to the nearest increment. + * Rounding is useful when you need to retain fidelity for small decimal numbers + * (eg. small fractions or percentages). + */ + function multiplyDecimalRoundPrecise(uint x, uint y) internal pure returns (uint) { + return _multiplyDecimalRound(x, y, PRECISE_UNIT); + } + + /** + * @return The result of safely multiplying x and y, interpreting the operands + * as fixed-point decimals of a standard unit. + * + * @dev The operands should be in the standard unit factor which will be + * divided out after the product of x and y is evaluated, so that product must be + * less than 2**256. + * + * Unlike multiplyDecimal, this function rounds the result to the nearest increment. + * Rounding is useful when you need to retain fidelity for small decimal numbers + * (eg. small fractions or percentages). + */ + function multiplyDecimalRound(uint x, uint y) internal pure returns (uint) { + return _multiplyDecimalRound(x, y, UNIT); + } + + /** + * @return The result of safely dividing x and y. The return value is a high + * precision decimal. + * + * @dev y is divided after the product of x and the standard precision unit + * is evaluated, so the product of x and UNIT must be less than 2**256. As + * this is an integer division, the result is always rounded down. + * This helps save on gas. Rounding is more expensive on gas. + */ + function divideDecimal(uint x, uint y) internal pure returns (uint) { + /* Reintroduce the UNIT factor that will be divided out by y. */ + return x.mul(UNIT).div(y); + } + + /** + * @return The result of safely dividing x and y. The return value is as a rounded + * decimal in the precision unit specified in the parameter. + * + * @dev y is divided after the product of x and the specified precision unit + * is evaluated, so the product of x and the specified precision unit must + * be less than 2**256. The result is rounded to the nearest increment. + */ + function _divideDecimalRound( + uint x, + uint y, + uint precisionUnit + ) private pure returns (uint) { + uint resultTimesTen = x.mul(precisionUnit * 10).div(y); + + if (resultTimesTen % 10 >= 5) { + resultTimesTen += 10; + } + + return resultTimesTen / 10; + } + + /** + * @return The result of safely dividing x and y. The return value is as a rounded + * standard precision decimal. + * + * @dev y is divided after the product of x and the standard precision unit + * is evaluated, so the product of x and the standard precision unit must + * be less than 2**256. The result is rounded to the nearest increment. + */ + function divideDecimalRound(uint x, uint y) internal pure returns (uint) { + return _divideDecimalRound(x, y, UNIT); + } + + /** + * @return The result of safely dividing x and y. The return value is as a rounded + * high precision decimal. + * + * @dev y is divided after the product of x and the high precision unit + * is evaluated, so the product of x and the high precision unit must + * be less than 2**256. The result is rounded to the nearest increment. + */ + function divideDecimalRoundPrecise(uint x, uint y) internal pure returns (uint) { + return _divideDecimalRound(x, y, PRECISE_UNIT); + } + + /** + * @dev Convert a standard decimal representation to a high precision one. + */ + function decimalToPreciseDecimal(uint i) internal pure returns (uint) { + return i.mul(UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR); + } + + /** + * @dev Convert a high precision decimal to a standard decimal representation. + */ + function preciseDecimalToDecimal(uint i) internal pure returns (uint) { + uint quotientTimesTen = i / (UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR / 10); + + if (quotientTimesTen % 10 >= 5) { + quotientTimesTen += 10; + } + + return quotientTimesTen / 10; + } + + /** + * @dev Round down the value with given number + */ + function roundDownDecimal(uint x, uint d) internal pure returns (uint) { + return x.div(10**d).mul(10**d); + } + + /** + * @dev Round up the value with given number + */ + function roundUpDecimal(uint x, uint d) internal pure returns (uint) { + uint _decimal = 10**d; + + if (x % _decimal > 0) { + x = x.add(10**d); + } + + return x.div(_decimal).mul(_decimal); + } +} + + +interface IVirtualPynth { + // Views + function balanceOfUnderlying(address account) external view returns (uint); + + function rate() external view returns (uint); + + function readyToSettle() external view returns (bool); + + function secsLeftInWaitingPeriod() external view returns (uint); + + function settled() external view returns (bool); + + function pynth() external view returns (IPynth); + + // Mutative functions + function settle(address account) external; +} + + +// https://docs.peri.finance/contracts/source/interfaces/iperiFinance +interface IPeriFinance { + // Views + function getRequiredAddress(bytes32 contractName) external view returns (address); + + function anyPynthOrPERIRateIsInvalid() external view returns (bool anyRateInvalid); + + function availableCurrencyKeys() external view returns (bytes32[] memory); + + function availablePynthCount() external view returns (uint); + + function availablePynths(uint index) external view returns (IPynth); + + function collateral(address account) external view returns (uint); + + function collateralisationRatio(address issuer) external view returns (uint); + + function debtBalanceOf(address issuer, bytes32 currencyKey) external view returns (uint); + + function isWaitingPeriod(bytes32 currencyKey) external view returns (bool); + + function maxIssuablePynths(address issuer) external view returns (uint maxIssuable); + + function externalTokenQuota( + address _account, + uint _additionalpUSD, + uint _additionalExToken, + bool _isIssue + ) external view returns (uint); + + function remainingIssuablePynths(address issuer) + external + view + returns ( + uint maxIssuable, + uint alreadyIssued, + uint totalSystemDebt + ); + + function maxExternalTokenStakeAmount(address _account, bytes32 _currencyKey) + external + view + returns (uint issueAmountToQuota, uint stakeAmountToQuota); + + function pynths(bytes32 currencyKey) external view returns (IPynth); + + function pynthsByAddress(address pynthAddress) external view returns (bytes32); + + function totalIssuedPynths(bytes32 currencyKey) external view returns (uint); + + function totalIssuedPynthsExcludeEtherCollateral(bytes32 currencyKey) external view returns (uint); + + function transferablePeriFinance(address account) external view returns (uint transferable); + + // Mutative Functions + function issuePynths(bytes32 _currencyKey, uint _issueAmount) external; + + function issueMaxPynths() external; + + function issuePynthsToMaxQuota(bytes32 _currencyKey) external; + + function burnPynths(bytes32 _currencyKey, uint _burnAmount) external; + + function fitToClaimable() external; + + function exit() external; + + function exchange( + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey + ) external returns (uint amountReceived); + + function exchangeOnBehalf( + address exchangeForAddress, + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey + ) external returns (uint amountReceived); + + function exchangeWithTracking( + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey, + address originator, + bytes32 trackingCode + ) external returns (uint amountReceived); + + function exchangeOnBehalfWithTracking( + address exchangeForAddress, + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey, + address originator, + bytes32 trackingCode + ) external returns (uint amountReceived); + + function exchangeWithVirtual( + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey, + bytes32 trackingCode + ) external returns (uint amountReceived, IVirtualPynth vPynth); + + function mint(address _user, uint _amount) external returns (bool); + + function inflationalMint(uint _networkDebtShare) external returns (bool); + + function settle(bytes32 currencyKey) + external + returns ( + uint reclaimed, + uint refunded, + uint numEntries + ); + + // Liquidations + function liquidateDelinquentAccount(address account, uint pusdAmount) external returns (bool); + + // Restricted Functions + + function mintSecondary(address account, uint amount) external; + + function mintSecondaryRewards(uint amount) external; + + function burnSecondary(address account, uint amount) external; +} + + +// https://docs.peri.finance/contracts/source/interfaces/ifeepool +interface IFeePool { + // Views + + // solhint-disable-next-line func-name-mixedcase + function FEE_ADDRESS() external view returns (address); + + function feesAvailable(address account) external view returns (uint, uint); + + function feePeriodDuration() external view returns (uint); + + function isFeesClaimable(address account) external view returns (bool); + + function targetThreshold() external view returns (uint); + + function totalFeesAvailable() external view returns (uint); + + function totalRewardsAvailable() external view returns (uint); + + // Mutative Functions + function claimFees() external returns (bool); + + function claimOnBehalf(address claimingForAddress) external returns (bool); + + function closeCurrentFeePeriod() external; + + // Restricted: used internally to PeriFinance + function appendAccountIssuanceRecord( + address account, + uint lockedAmount, + uint debtEntryIndex + ) external; + + function recordFeePaid(uint pUSDAmount) external; + + function setRewardsToDistribute(uint amount) external; +} + + +// https://docs.peri.finance/contracts/source/interfaces/iperiFinancestate +interface IPeriFinanceState { + // Views + function debtLedger(uint index) external view returns (uint); + + function issuanceData(address account) external view returns (uint initialDebtOwnership, uint debtEntryIndex); + + function debtLedgerLength() external view returns (uint); + + function hasIssued(address account) external view returns (bool); + + function lastDebtLedgerEntry() external view returns (uint); + + // Mutative functions + function incrementTotalIssuerCount() external; + + function decrementTotalIssuerCount() external; + + function setCurrentIssuanceData(address account, uint initialDebtOwnership) external; + + function appendDebtLedgerValue(uint value) external; + + function clearIssuanceData(address account) external; +} + + +// https://docs.peri.finance/contracts/source/interfaces/iexchanger +interface IExchanger { + // Views + function calculateAmountAfterSettlement( + address from, + bytes32 currencyKey, + uint amount, + uint refunded + ) external view returns (uint amountAfterSettlement); + + function isPynthRateInvalid(bytes32 currencyKey) external view returns (bool); + + function maxSecsLeftInWaitingPeriod(address account, bytes32 currencyKey) external view returns (uint); + + function settlementOwing(address account, bytes32 currencyKey) + external + view + returns ( + uint reclaimAmount, + uint rebateAmount, + uint numEntries + ); + + function hasWaitingPeriodOrSettlementOwing(address account, bytes32 currencyKey) external view returns (bool); + + function feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) + external + view + returns (uint exchangeFeeRate); + + function getAmountsForExchange( + uint sourceAmount, + bytes32 sourceCurrencyKey, + bytes32 destinationCurrencyKey + ) + external + view + returns ( + uint amountReceived, + uint fee, + uint exchangeFeeRate + ); + + function priceDeviationThresholdFactor() external view returns (uint); + + function waitingPeriodSecs() external view returns (uint); + + // Mutative functions + function exchange( + address from, + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey, + address destinationAddress + ) external returns (uint amountReceived); + + function exchangeOnBehalf( + address exchangeForAddress, + address from, + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey + ) external returns (uint amountReceived); + + function exchangeWithTracking( + address from, + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey, + address destinationAddress, + address originator, + bytes32 trackingCode + ) external returns (uint amountReceived); + + function exchangeOnBehalfWithTracking( + address exchangeForAddress, + address from, + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey, + address originator, + bytes32 trackingCode + ) external returns (uint amountReceived); + + function exchangeWithVirtual( + address from, + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey, + address destinationAddress, + bytes32 trackingCode + ) external returns (uint amountReceived, IVirtualPynth vPynth); + + function settle(address from, bytes32 currencyKey) + external + returns ( + uint reclaimed, + uint refunded, + uint numEntries + ); + + function setLastExchangeRateForPynth(bytes32 currencyKey, uint rate) external; + + function suspendPynthWithInvalidRate(bytes32 currencyKey) external; +} + + +// https://docs.peri.finance/contracts/source/interfaces/idelegateapprovals +interface IDelegateApprovals { + // Views + function canBurnFor(address authoriser, address delegate) external view returns (bool); + + function canIssueFor(address authoriser, address delegate) external view returns (bool); + + function canClaimFor(address authoriser, address delegate) external view returns (bool); + + function canExchangeFor(address authoriser, address delegate) external view returns (bool); + + // Mutative + function approveAllDelegatePowers(address delegate) external; + + function removeAllDelegatePowers(address delegate) external; + + function approveBurnOnBehalf(address delegate) external; + + function removeBurnOnBehalf(address delegate) external; + + function approveIssueOnBehalf(address delegate) external; + + function removeIssueOnBehalf(address delegate) external; + + function approveClaimOnBehalf(address delegate) external; + + function removeClaimOnBehalf(address delegate) external; + + function approveExchangeOnBehalf(address delegate) external; + + function removeExchangeOnBehalf(address delegate) external; +} + + +// https://docs.peri.finance/contracts/source/interfaces/iexchangerates +interface IExchangeRates { + // Structs + struct RateAndUpdatedTime { + uint216 rate; + uint40 time; + } + + struct InversePricing { + uint entryPoint; + uint upperLimit; + uint lowerLimit; + bool frozenAtUpperLimit; + bool frozenAtLowerLimit; + } + + // Views + function aggregators(bytes32 currencyKey) external view returns (address); + + function aggregatorWarningFlags() external view returns (address); + + function anyRateIsInvalid(bytes32[] calldata currencyKeys) external view returns (bool); + + function canFreezeRate(bytes32 currencyKey) external view returns (bool); + + function currentRoundForRate(bytes32 currencyKey) external view returns (uint); + + function currenciesUsingAggregator(address aggregator) external view returns (bytes32[] memory); + + function effectiveValue( + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey + ) external view returns (uint value); + + function effectiveValueAndRates( + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey + ) + external + view + returns ( + uint value, + uint sourceRate, + uint destinationRate + ); + + function effectiveValueAtRound( + bytes32 sourceCurrencyKey, + uint sourceAmount, + bytes32 destinationCurrencyKey, + uint roundIdForSrc, + uint roundIdForDest + ) external view returns (uint value); + + function getCurrentRoundId(bytes32 currencyKey) external view returns (uint); + + function getLastRoundIdBeforeElapsedSecs( + bytes32 currencyKey, + uint startingRoundId, + uint startingTimestamp, + uint timediff + ) external view returns (uint); + + function inversePricing(bytes32 currencyKey) + external + view + returns ( + uint entryPoint, + uint upperLimit, + uint lowerLimit, + bool frozenAtUpperLimit, + bool frozenAtLowerLimit + ); + + function lastRateUpdateTimes(bytes32 currencyKey) external view returns (uint256); + + function oracle() external view returns (address); + + function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time); + + function rateAndUpdatedTime(bytes32 currencyKey) external view returns (uint rate, uint time); + + function rateAndInvalid(bytes32 currencyKey) external view returns (uint rate, bool isInvalid); + + function rateForCurrency(bytes32 currencyKey) external view returns (uint); + + function rateIsFlagged(bytes32 currencyKey) external view returns (bool); + + function rateIsFrozen(bytes32 currencyKey) external view returns (bool); + + function rateIsInvalid(bytes32 currencyKey) external view returns (bool); + + function rateIsStale(bytes32 currencyKey) external view returns (bool); + + function rateStalePeriod() external view returns (uint); + + function ratesAndUpdatedTimeForCurrencyLastNRounds(bytes32 currencyKey, uint numRounds) + external + view + returns (uint[] memory rates, uint[] memory times); + + function ratesAndInvalidForCurrencies(bytes32[] calldata currencyKeys) + external + view + returns (uint[] memory rates, bool anyRateInvalid); + + function ratesForCurrencies(bytes32[] calldata currencyKeys) external view returns (uint[] memory); + + // Mutative functions + function freezeRate(bytes32 currencyKey) external; +} + + +// https://docs.peri.finance/contracts/source/interfaces/iethercollateral +interface IEtherCollateral { + // Views + function totalIssuedPynths() external view returns (uint256); + + function totalLoansCreated() external view returns (uint256); + + function totalOpenLoanCount() external view returns (uint256); + + // Mutative functions + function openLoan() external payable returns (uint256 loanID); + + function closeLoan(uint256 loanID) external; + + function liquidateUnclosedLoan(address _loanCreatorsAddress, uint256 _loanID) external; +} + + +// https://docs.peri.finance/contracts/source/interfaces/iethercollateralsusd +interface IEtherCollateralpUSD { + // Views + function totalIssuedPynths() external view returns (uint256); + + function totalLoansCreated() external view returns (uint256); + + function totalOpenLoanCount() external view returns (uint256); + + // Mutative functions + function openLoan(uint256 _loanAmount) external payable returns (uint256 loanID); + + function closeLoan(uint256 loanID) external; + + function liquidateUnclosedLoan(address _loanCreatorsAddress, uint256 _loanID) external; + + function depositCollateral(address account, uint256 loanID) external payable; + + function withdrawCollateral(uint256 loanID, uint256 withdrawAmount) external; + + function repayLoan( + address _loanCreatorsAddress, + uint256 _loanID, + uint256 _repayAmount + ) external; +} + + +// https://docs.peri.finance/contracts/source/interfaces/ihasbalance +interface IHasBalance { + // Views + function balanceOf(address account) external view returns (uint); +} + + +// https://docs.peri.finance/contracts/source/interfaces/ierc20 +interface IERC20 { + // ERC20 Optional Views + function name() external view returns (string memory); + + function symbol() external view returns (string memory); + + function decimals() external view returns (uint8); + + // Views + function totalSupply() external view returns (uint); + + function balanceOf(address owner) external view returns (uint); + + function allowance(address owner, address spender) external view returns (uint); + + // Mutative functions + function transfer(address to, uint value) external returns (bool); + + function approve(address spender, uint value) external returns (bool); + + function transferFrom( + address from, + address to, + uint value + ) external returns (bool); + + // Events + event Transfer(address indexed from, address indexed to, uint value); + + event Approval(address indexed owner, address indexed spender, uint value); +} + + +// https://docs.peri.finance/contracts/source/interfaces/iliquidations +interface ILiquidations { + // Views + function isOpenForLiquidation(address account) external view returns (bool); + + function getLiquidationDeadlineForAccount(address account) external view returns (uint); + + function isLiquidationDeadlinePassed(address account) external view returns (bool); + + function liquidationDelay() external view returns (uint); + + function liquidationRatio() external view returns (uint); + + function liquidationPenalty() external view returns (uint); + + function calculateAmountToFixCollateral(uint debtBalance, uint collateral) external view returns (uint); + + // Mutative Functions + function flagAccountForLiquidation(address account) external; + + // Restricted: used internally to PeriFinance + function removeAccountInLiquidation(address account) external; + + function checkAndRemoveAccountInLiquidation(address account) external; +} + + +interface ICollateralManager { + // Manager information + function hasCollateral(address collateral) external view returns (bool); + + function isPynthManaged(bytes32 currencyKey) external view returns (bool); + + // State information + function long(bytes32 pynth) external view returns (uint amount); + + function short(bytes32 pynth) external view returns (uint amount); + + function totalLong() external view returns (uint pusdValue, bool anyRateIsInvalid); + + function totalShort() external view returns (uint pusdValue, bool anyRateIsInvalid); + + function getBorrowRate() external view returns (uint borrowRate, bool anyRateIsInvalid); + + function getShortRate(bytes32 pynth) external view returns (uint shortRate, bool rateIsInvalid); + + function getRatesAndTime(uint index) + external + view + returns ( + uint entryRate, + uint lastRate, + uint lastUpdated, + uint newIndex + ); + + function getShortRatesAndTime(bytes32 currency, uint index) + external + view + returns ( + uint entryRate, + uint lastRate, + uint lastUpdated, + uint newIndex + ); + + function exceedsDebtLimit(uint amount, bytes32 currency) external view returns (bool canIssue, bool anyRateIsInvalid); + + function arePynthsAndCurrenciesSet(bytes32[] calldata requiredPynthNamesInResolver, bytes32[] calldata pynthKeys) + external + view + returns (bool); + + function areShortablePynthsSet(bytes32[] calldata requiredPynthNamesInResolver, bytes32[] calldata pynthKeys) + external + view + returns (bool); + + // Loans + function getNewLoanId() external returns (uint id); + + // Manager mutative + function addCollaterals(address[] calldata collaterals) external; + + function removeCollaterals(address[] calldata collaterals) external; + + function addPynths(bytes32[] calldata pynthNamesInResolver, bytes32[] calldata pynthKeys) external; + + function removePynths(bytes32[] calldata pynths, bytes32[] calldata pynthKeys) external; + + function addShortablePynths(bytes32[2][] calldata requiredPynthAndInverseNamesInResolver, bytes32[] calldata pynthKeys) + external; + + function removeShortablePynths(bytes32[] calldata pynths) external; + + // State mutative + function updateBorrowRates(uint rate) external; + + function updateShortRates(bytes32 currency, uint rate) external; + + function incrementLongs(bytes32 pynth, uint amount) external; + + function decrementLongs(bytes32 pynth, uint amount) external; + + function incrementShorts(bytes32 pynth, uint amount) external; + + function decrementShorts(bytes32 pynth, uint amount) external; +} + + +contract IExternalTokenStakeManager { + function stake( + address _staker, + uint _amount, + bytes32 _targetCurrency, + bytes32 _inputCurrency + ) external; + + function unstake( + address _unstaker, + uint _amount, + bytes32 _targetCurrency, + bytes32 _inputCurrency + ) external; + + function unstakeMultipleTokens( + address _unstaker, + uint _amount, + bytes32 _inputCurrency + ) external; + + function getTokenList() external view returns (bytes32[] memory); + + function getTokenAddress(bytes32 _currencyKey) external view returns (address); + + function getTokenDecimals(bytes32 _currencyKey) external view returns (uint8); + + function getTokenActivation(bytes32 _currencyKey) external view returns (bool); + + function getCurrencyKeyOrder() external view returns (bytes32[] memory); + + function combinedStakedAmountOf(address _user, bytes32 _unitCurrency) external view returns (uint); + + function stakedAmountOf( + address _user, + bytes32 _currencyKey, + bytes32 _unitCurrency + ) external view returns (uint); +} + + +// Inheritance + + +// Libraries + + +// Internal references + + +interface IRewardEscrowV2 { + // Views + function balanceOf(address account) external view returns (uint); +} + +interface IIssuerInternalDebtCache { + function updateCachedPynthDebtWithRate(bytes32 currencyKey, uint currencyRate) external; + + function updateCachedPynthDebtsWithRates(bytes32[] calldata currencyKeys, uint[] calldata currencyRates) external; + + function updateDebtCacheValidity(bool currentlyInvalid) external; + + function cacheInfo() + external + view + returns ( + uint cachedDebt, + uint timestamp, + bool isInvalid, + bool isStale + ); +} + +// https://docs.peri.finance/contracts/source/contracts/issuer +contract Issuer is Owned, MixinSystemSettings, IIssuer { + using SafeMath for uint; + using SafeDecimalMath for uint; + + // Available Pynths which can be used with the system + IPynth[] public availablePynths; + mapping(bytes32 => IPynth) public pynths; + mapping(address => bytes32) public pynthsByAddress; + + /* ========== ENCODED NAMES ========== */ + + bytes32 internal constant pUSD = "pUSD"; + bytes32 internal constant pETH = "pETH"; + bytes32 internal constant PERI = "PERI"; + bytes32 internal constant USDC = "USDC"; + + // Flexible storage names + + bytes32 public constant CONTRACT_NAME = "Issuer"; + bytes32 internal constant LAST_ISSUE_EVENT = "lastIssueEvent"; + + /* ========== ADDRESS RESOLVER CONFIGURATION ========== */ + + bytes32 private constant CONTRACT_PERIFINANCE = "PeriFinance"; + bytes32 private constant CONTRACT_EXCHANGER = "Exchanger"; + bytes32 private constant CONTRACT_EXRATES = "ExchangeRates"; + bytes32 private constant CONTRACT_PERIFINANCESTATE = "PeriFinanceState"; + bytes32 private constant CONTRACT_FEEPOOL = "FeePool"; + bytes32 private constant CONTRACT_DELEGATEAPPROVALS = "DelegateApprovals"; + bytes32 private constant CONTRACT_ETHERCOLLATERAL = "EtherCollateral"; + bytes32 private constant CONTRACT_ETHERCOLLATERAL_PUSD = "EtherCollateralpUSD"; + bytes32 private constant CONTRACT_COLLATERALMANAGER = "CollateralManager"; + bytes32 private constant CONTRACT_REWARDESCROW_V2 = "RewardEscrowV2"; + bytes32 private constant CONTRACT_PERIFINANCEESCROW = "PeriFinanceEscrow"; + bytes32 private constant CONTRACT_LIQUIDATIONS = "Liquidations"; + bytes32 private constant CONTRACT_DEBTCACHE = "DebtCache"; + bytes32 private constant CONTRACT_EXTOKENSTAKEMANAGER = "ExternalTokenStakeManager"; + + constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {} + + /* ========== VIEWS ========== */ + function resolverAddressesRequired() public view returns (bytes32[] memory addresses) { + bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired(); + bytes32[] memory newAddresses = new bytes32[](14); + newAddresses[0] = CONTRACT_PERIFINANCE; + newAddresses[1] = CONTRACT_EXCHANGER; + newAddresses[2] = CONTRACT_EXRATES; + newAddresses[3] = CONTRACT_PERIFINANCESTATE; + newAddresses[4] = CONTRACT_FEEPOOL; + newAddresses[5] = CONTRACT_DELEGATEAPPROVALS; + newAddresses[6] = CONTRACT_ETHERCOLLATERAL; + newAddresses[7] = CONTRACT_ETHERCOLLATERAL_PUSD; + newAddresses[8] = CONTRACT_REWARDESCROW_V2; + newAddresses[9] = CONTRACT_PERIFINANCEESCROW; + newAddresses[10] = CONTRACT_LIQUIDATIONS; + newAddresses[11] = CONTRACT_DEBTCACHE; + newAddresses[12] = CONTRACT_COLLATERALMANAGER; + newAddresses[13] = CONTRACT_EXTOKENSTAKEMANAGER; + return combineArrays(existingAddresses, newAddresses); + } + + function periFinance() internal view returns (IPeriFinance) { + return IPeriFinance(requireAndGetAddress(CONTRACT_PERIFINANCE)); + } + + function exTokenStakeManager() internal view returns (IExternalTokenStakeManager) { + return IExternalTokenStakeManager(requireAndGetAddress(CONTRACT_EXTOKENSTAKEMANAGER)); + } + + function exchanger() internal view returns (IExchanger) { + return IExchanger(requireAndGetAddress(CONTRACT_EXCHANGER)); + } + + function exchangeRates() internal view returns (IExchangeRates) { + return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES)); + } + + function periFinanceState() internal view returns (IPeriFinanceState) { + return IPeriFinanceState(requireAndGetAddress(CONTRACT_PERIFINANCESTATE)); + } + + function feePool() internal view returns (IFeePool) { + return IFeePool(requireAndGetAddress(CONTRACT_FEEPOOL)); + } + + function liquidations() internal view returns (ILiquidations) { + return ILiquidations(requireAndGetAddress(CONTRACT_LIQUIDATIONS)); + } + + function delegateApprovals() internal view returns (IDelegateApprovals) { + return IDelegateApprovals(requireAndGetAddress(CONTRACT_DELEGATEAPPROVALS)); + } + + function etherCollateral() internal view returns (IEtherCollateral) { + return IEtherCollateral(requireAndGetAddress(CONTRACT_ETHERCOLLATERAL)); + } + + function etherCollateralpUSD() internal view returns (IEtherCollateralpUSD) { + return IEtherCollateralpUSD(requireAndGetAddress(CONTRACT_ETHERCOLLATERAL_PUSD)); + } + + function collateralManager() internal view returns (ICollateralManager) { + return ICollateralManager(requireAndGetAddress(CONTRACT_COLLATERALMANAGER)); + } + + function rewardEscrowV2() internal view returns (IRewardEscrowV2) { + return IRewardEscrowV2(requireAndGetAddress(CONTRACT_REWARDESCROW_V2)); + } + + function periFinanceEscrow() internal view returns (IHasBalance) { + return IHasBalance(requireAndGetAddress(CONTRACT_PERIFINANCEESCROW)); + } + + function debtCache() internal view returns (IIssuerInternalDebtCache) { + return IIssuerInternalDebtCache(requireAndGetAddress(CONTRACT_DEBTCACHE)); + } + + function issuanceRatio() external view returns (uint) { + return getIssuanceRatio(); + } + + function externalTokenLimit() external view returns (uint) { + return getExternalTokenQuota(); + } + + function _availableCurrencyKeysWithOptionalPERI(bool withPERI) internal view returns (bytes32[] memory) { + bytes32[] memory currencyKeys = new bytes32[](availablePynths.length + (withPERI ? 1 : 0)); + + for (uint i = 0; i < availablePynths.length; i++) { + currencyKeys[i] = pynthsByAddress[address(availablePynths[i])]; + } + + if (withPERI) { + currencyKeys[availablePynths.length] = PERI; + } + + return currencyKeys; + } + + function _totalIssuedPynths(bytes32 currencyKey, bool excludeCollateral) + internal + view + returns (uint totalIssued, bool anyRateIsInvalid) + { + (uint debt, , bool cacheIsInvalid, bool cacheIsStale) = debtCache().cacheInfo(); + anyRateIsInvalid = cacheIsInvalid || cacheIsStale; + + IExchangeRates exRates = exchangeRates(); + + // Add total issued pynths from non peri collateral back into the total if not excluded + if (!excludeCollateral) { + // Get the pUSD equivalent amount of all the MC issued pynths. + (uint nonPeriDebt, bool invalid) = collateralManager().totalLong(); + debt = debt.add(nonPeriDebt); + anyRateIsInvalid = anyRateIsInvalid || invalid; + + // Now add the ether collateral stuff as we are still supporting it. + debt = debt.add(etherCollateralpUSD().totalIssuedPynths()); + + // Add ether collateral pETH + (uint ethRate, bool ethRateInvalid) = exRates.rateAndInvalid(pETH); + uint ethIssuedDebt = etherCollateral().totalIssuedPynths().multiplyDecimalRound(ethRate); + debt = debt.add(ethIssuedDebt); + anyRateIsInvalid = anyRateIsInvalid || ethRateInvalid; + } + + if (currencyKey == pUSD) { + return (debt, anyRateIsInvalid); + } + + (uint currencyRate, bool currencyRateInvalid) = exRates.rateAndInvalid(currencyKey); + return (debt.divideDecimalRound(currencyRate), anyRateIsInvalid || currencyRateInvalid); + } + + function _debtBalanceOfAndTotalDebt(address _issuer, bytes32 currencyKey) + internal + view + returns ( + uint debtBalance, + uint totalSystemValue, + bool anyRateIsInvalid + ) + { + IPeriFinanceState state = periFinanceState(); + + // What was their initial debt ownership? + (uint initialDebtOwnership, uint debtEntryIndex) = state.issuanceData(_issuer); + + // What's the total value of the system excluding ETH backed pynths in their requested currency? + (totalSystemValue, anyRateIsInvalid) = _totalIssuedPynths(currencyKey, true); + + // If it's zero, they haven't issued, and they have no debt. + // Note: it's more gas intensive to put this check here rather than before _totalIssuedPynths + // if they have 0 PERI, but it's a necessary trade-off + if (initialDebtOwnership == 0) return (0, totalSystemValue, anyRateIsInvalid); + + // Figure out the global debt percentage delta from when they entered the system. + // This is a high precision integer of 27 (1e27) decimals. + uint _debtLedgerLength = state.debtLedgerLength(); + uint systemDebt = state.debtLedger(debtEntryIndex); + uint currentDebtOwnership; + if (_debtLedgerLength == 0 || systemDebt == 0) { + currentDebtOwnership = 0; + } else { + currentDebtOwnership = state + .lastDebtLedgerEntry() + .divideDecimalRoundPrecise(systemDebt) + .multiplyDecimalRoundPrecise(initialDebtOwnership); + } + + // Their debt balance is their portion of the total system value. + uint highPrecisionBalance = + totalSystemValue.decimalToPreciseDecimal().multiplyDecimalRoundPrecise(currentDebtOwnership); + + // Convert back into 18 decimals (1e18) + debtBalance = highPrecisionBalance.preciseDecimalToDecimal(); + } + + function _canBurnPynths(address account) internal view returns (bool) { + return now >= _lastIssueEvent(account).add(getMinimumStakeTime()); + } + + function _lastIssueEvent(address account) internal view returns (uint) { + // Get the timestamp of the last issue this account made + return flexibleStorage().getUIntValue(CONTRACT_NAME, keccak256(abi.encodePacked(LAST_ISSUE_EVENT, account))); + } + + function _remainingIssuablePynths(address _issuer) + internal + view + returns ( + uint maxIssuable, + uint alreadyIssued, + uint totalSystemDebt, + bool anyRateIsInvalid + ) + { + (alreadyIssued, totalSystemDebt, anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_issuer, pUSD); + (uint issuable, bool isInvalid) = _maxIssuablePynths(_issuer); + maxIssuable = issuable; + anyRateIsInvalid = anyRateIsInvalid || isInvalid; + + if (alreadyIssued >= maxIssuable) { + maxIssuable = 0; + } else { + maxIssuable = maxIssuable.sub(alreadyIssued); + } + } + + function _periToUSD(uint amount, uint periRate) internal pure returns (uint) { + return amount.multiplyDecimalRound(periRate); + } + + function _usdToPeri(uint amount, uint periRate) internal pure returns (uint) { + return amount.divideDecimalRound(periRate); + } + + function _maxIssuablePynths(address _issuer) internal view returns (uint, bool) { + // What is the value of their PERI balance in pUSD + (uint periRate, bool periRateIsInvalid) = exchangeRates().rateAndInvalid(PERI); + uint periCollateral = _periToUSD(_collateral(_issuer), periRate); + + uint externalTokenStaked = exTokenStakeManager().combinedStakedAmountOf(_issuer, pUSD); + + uint destinationValue = periCollateral.add(externalTokenStaked); + + // They're allowed to issue up to issuanceRatio of that value + return (destinationValue.multiplyDecimal(getIssuanceRatio()), periRateIsInvalid); + } + + function _collateralisationRatio(address _issuer) internal view returns (uint, bool) { + uint totalOwnedPeriFinance = _collateral(_issuer); + uint externalTokenStaked = exTokenStakeManager().combinedStakedAmountOf(_issuer, PERI); + + (uint debtBalance, , bool anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_issuer, PERI); + + // it's more gas intensive to put this check here if they have 0 PERI, but it complies with the interface + if (totalOwnedPeriFinance == 0 && externalTokenStaked == 0) return (0, anyRateIsInvalid); + + uint totalOwned = totalOwnedPeriFinance.add(externalTokenStaked); + + return (debtBalance.divideDecimal(totalOwned), anyRateIsInvalid); + } + + function _collateral(address account) internal view returns (uint) { + uint balance = IERC20(address(periFinance())).balanceOf(account); + + if (address(periFinanceEscrow()) != address(0)) { + balance = balance.add(periFinanceEscrow().balanceOf(account)); + } + + if (address(rewardEscrowV2()) != address(0)) { + balance = balance.add(rewardEscrowV2().balanceOf(account)); + } + + return balance; + } + + /** + * @notice It calculates the quota of user's staked amount to the debt. + * If parameters are not 0, it estimates the quota assuming those value is applied to current status. + * + * @param _account account + * @param _debtBalance Debt balance to estimate [USD] + * @param _additionalpUSD The pUSD value to be applied for estimation [USD] + * @param _additionalExToken The external token stake amount to be applied for estimation [USD] + * @param _isIssue If true, it is considered issueing/staking estimation. + */ + function _externalTokenQuota( + address _account, + uint _debtBalance, + uint _additionalpUSD, + uint _additionalExToken, + bool _isIssue + ) internal view returns (uint) { + uint combinedStakedAmount = exTokenStakeManager().combinedStakedAmountOf(_account, pUSD); + + if (_debtBalance == 0 || combinedStakedAmount == 0) { + return 0; + } + + if (_isIssue) { + _debtBalance = _debtBalance.add(_additionalpUSD); + combinedStakedAmount = combinedStakedAmount.add(_additionalExToken); + } else { + _debtBalance = _debtBalance.sub(_additionalpUSD); + combinedStakedAmount = combinedStakedAmount.sub(_additionalExToken); + } + + return combinedStakedAmount.divideDecimalRound(_debtBalance.divideDecimalRound(getIssuanceRatio())); + } + + function _amountsToFitClaimable( + uint _currentDebt, + uint _stakedExTokenAmount, + uint _periCollateral + ) internal view returns (uint burnAmount, uint exTokenAmountToUnstake) { + uint targetRatio = getIssuanceRatio(); + uint exTokenQuota = getExternalTokenQuota(); + + uint initialCRatio = _currentDebt.divideDecimal(_stakedExTokenAmount.add(_periCollateral)); + // it doesn't satisfy target c-ratio + if (initialCRatio > targetRatio) { + uint maxAllowedExTokenStakeAmountByPeriCollateral = + _periCollateral.multiplyDecimal(exTokenQuota.divideDecimal(SafeDecimalMath.unit().sub(exTokenQuota))); + exTokenAmountToUnstake = _stakedExTokenAmount > maxAllowedExTokenStakeAmountByPeriCollateral + ? _stakedExTokenAmount.sub(maxAllowedExTokenStakeAmountByPeriCollateral) + : 0; + burnAmount = _currentDebt.sub( + _periCollateral.add(_stakedExTokenAmount).sub(exTokenAmountToUnstake).multiplyDecimal(targetRatio) + ); + + // it satisfies target c-ratio but violates external token quota + } else { + uint currentExTokenQuota = _stakedExTokenAmount.multiplyDecimal(targetRatio).divideDecimal(_currentDebt); + require(currentExTokenQuota > exTokenQuota, "Account is already claimable"); + + burnAmount = (_stakedExTokenAmount.multiplyDecimal(targetRatio).sub(_currentDebt.multiplyDecimal(exTokenQuota))) + .divideDecimal(SafeDecimalMath.unit().sub(exTokenQuota)); + exTokenAmountToUnstake = burnAmount.divideDecimal(targetRatio); + } + } + + /** + * @notice It calculates maximum issue/stake(external token) amount to meet external token quota limit. + * + * @param _from target address + * @param _debtBalance current debt balance[pUSD] + * @param _stakedAmount currently target address's external token staked amount[pUSD] + * @param _currencyKey currency key of external token to stake + */ + function _maxExternalTokenStakeAmount( + address _from, + uint _debtBalance, + uint _stakedAmount, + bytes32 _currencyKey + ) internal view returns (uint issueAmount, uint stakeAmount) { + uint targetRatio = getIssuanceRatio(); + uint quotaLimit = getExternalTokenQuota(); + + uint maxAllowedStakingAmount = _debtBalance.multiplyDecimal(quotaLimit).divideDecimal(targetRatio); + if (_stakedAmount >= maxAllowedStakingAmount) { + return (0, 0); + } + + stakeAmount = ((maxAllowedStakingAmount).sub(_stakedAmount)).divideDecimal(SafeDecimalMath.unit().sub(quotaLimit)); + + uint balance = IERC20(exTokenStakeManager().getTokenAddress(_currencyKey)).balanceOf(_from); + stakeAmount = balance < stakeAmount ? balance : stakeAmount; + issueAmount = stakeAmount.multiplyDecimal(targetRatio); + } + + function minimumStakeTime() external view returns (uint) { + return getMinimumStakeTime(); + } + + function canBurnPynths(address account) external view returns (bool) { + return _canBurnPynths(account); + } + + function availableCurrencyKeys() external view returns (bytes32[] memory) { + return _availableCurrencyKeysWithOptionalPERI(false); + } + + function availablePynthCount() external view returns (uint) { + return availablePynths.length; + } + + function anyPynthOrPERIRateIsInvalid() external view returns (bool anyRateInvalid) { + (, anyRateInvalid) = exchangeRates().ratesAndInvalidForCurrencies(_availableCurrencyKeysWithOptionalPERI(true)); + } + + function totalIssuedPynths(bytes32 currencyKey, bool excludeEtherCollateral) external view returns (uint totalIssued) { + (totalIssued, ) = _totalIssuedPynths(currencyKey, excludeEtherCollateral); + } + + function lastIssueEvent(address account) external view returns (uint) { + return _lastIssueEvent(account); + } + + function collateralisationRatio(address _issuer) external view returns (uint cratio) { + (cratio, ) = _collateralisationRatio(_issuer); + } + + function collateralisationRatioAndAnyRatesInvalid(address _issuer) + external + view + returns (uint cratio, bool anyRateIsInvalid) + { + return _collateralisationRatio(_issuer); + } + + function collateral(address account) external view returns (uint) { + return _collateral(account); + } + + function debtBalanceOf(address _issuer, bytes32 currencyKey) external view returns (uint debtBalance) { + IPeriFinanceState state = periFinanceState(); + + // What was their initial debt ownership? + (uint initialDebtOwnership, ) = state.issuanceData(_issuer); + + // If it's zero, they haven't issued, and they have no debt. + if (initialDebtOwnership == 0) return 0; + + (debtBalance, , ) = _debtBalanceOfAndTotalDebt(_issuer, currencyKey); + } + + function remainingIssuablePynths(address _issuer) + external + view + returns ( + uint maxIssuable, + uint alreadyIssued, + uint totalSystemDebt + ) + { + (maxIssuable, alreadyIssued, totalSystemDebt, ) = _remainingIssuablePynths(_issuer); + } + + function maxIssuablePynths(address _issuer) external view returns (uint) { + (uint maxIssuable, ) = _maxIssuablePynths(_issuer); + return maxIssuable; + } + + function externalTokenQuota( + address _account, + uint _additionalpUSD, + uint _additionalExToken, + bool _isIssue + ) external view returns (uint) { + (uint debtBalance, , bool anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_account, pUSD); + + _requireRatesNotInvalid(anyRateIsInvalid); + + uint estimatedQuota = _externalTokenQuota(_account, debtBalance, _additionalpUSD, _additionalExToken, _isIssue); + + return estimatedQuota; + } + + function maxExternalTokenStakeAmount(address _account, bytes32 _currencyKey) + external + view + returns (uint issueAmountToQuota, uint stakeAmountToQuota) + { + (uint debtBalance, , ) = _debtBalanceOfAndTotalDebt(_account, pUSD); + + uint combinedStakedAmount = exTokenStakeManager().combinedStakedAmountOf(_account, pUSD); + + (issueAmountToQuota, stakeAmountToQuota) = _maxExternalTokenStakeAmount( + _account, + debtBalance, + combinedStakedAmount, + _currencyKey + ); + } + + function transferablePeriFinanceAndAnyRateIsInvalid(address account, uint balance) + external + view + returns (uint transferable, bool anyRateIsInvalid) + { + // How many PERI do they have, excluding escrow? + // Note: We're excluding escrow here because we're interested in their transferable amount + // and escrowed PERI are not transferable. + + // How many of those will be locked by the amount they've issued? + // Assuming issuance ratio is 20%, then issuing 20 PERI of value would require + // 100 PERI to be locked in their wallet to maintain their collateralisation ratio + // The locked periFinance value can exceed their balance. + (uint debtBalance, , bool rateIsInvalid) = _debtBalanceOfAndTotalDebt(account, PERI); + + uint debtAppliedIssuanceRatio = debtBalance.divideDecimalRound(getIssuanceRatio()); + + uint externalTokenStaked = exTokenStakeManager().combinedStakedAmountOf(account, PERI); + + // If external token staked balance is larger than required collateral amount for current debt, + // no PERI would be locked. (But it violates external token staking quota rule) + uint lockedPeriFinanceValue = + debtAppliedIssuanceRatio > externalTokenStaked ? debtAppliedIssuanceRatio.sub(externalTokenStaked) : 0; + + // If we exceed the balance, no PERI are transferable, otherwise the difference is. + if (lockedPeriFinanceValue >= balance) { + transferable = 0; + } else { + transferable = balance.sub(lockedPeriFinanceValue); + } + + anyRateIsInvalid = rateIsInvalid; + } + + function getPynths(bytes32[] calldata currencyKeys) external view returns (IPynth[] memory) { + uint numKeys = currencyKeys.length; + IPynth[] memory addresses = new IPynth[](numKeys); + + for (uint i = 0; i < numKeys; i++) { + addresses[i] = pynths[currencyKeys[i]]; + } + + return addresses; + } + + /* ========== MUTATIVE FUNCTIONS ========== */ + + function _addPynth(IPynth pynth) internal { + bytes32 currencyKey = pynth.currencyKey(); + require(pynths[currencyKey] == IPynth(0), "Pynth exists"); + require(pynthsByAddress[address(pynth)] == bytes32(0), "Pynth address already exists"); + + availablePynths.push(pynth); + pynths[currencyKey] = pynth; + pynthsByAddress[address(pynth)] = currencyKey; + + emit PynthAdded(currencyKey, address(pynth)); + } + + function addPynth(IPynth pynth) external onlyOwner { + _addPynth(pynth); + // Invalidate the cache to force a snapshot to be recomputed. If a pynth were to be added + // back to the system and it still somehow had cached debt, this would force the value to be + // updated. + debtCache().updateDebtCacheValidity(true); + } + + function addPynths(IPynth[] calldata pynthsToAdd) external onlyOwner { + uint numPynths = pynthsToAdd.length; + for (uint i = 0; i < numPynths; i++) { + _addPynth(pynthsToAdd[i]); + } + + // Invalidate the cache to force a snapshot to be recomputed. + debtCache().updateDebtCacheValidity(true); + } + + function _removePynth(bytes32 currencyKey) internal { + address pynthToRemove = address(pynths[currencyKey]); + require(pynthToRemove != address(0), "Pynth does not exist"); + require(IERC20(pynthToRemove).totalSupply() == 0, "Pynth supply exists"); + require(currencyKey != pUSD, "Cannot remove pynth"); + + // Remove the pynth from the availablePynths array. + for (uint i = 0; i < availablePynths.length; i++) { + if (address(availablePynths[i]) == pynthToRemove) { + delete availablePynths[i]; + + // Copy the last pynth into the place of the one we just deleted + // If there's only one pynth, this is pynths[0] = pynths[0]. + // If we're deleting the last one, it's also a NOOP in the same way. + availablePynths[i] = availablePynths[availablePynths.length - 1]; + + // Decrease the size of the array by one. + availablePynths.length--; + + break; + } + } + + // And remove it from the pynths mapping + delete pynthsByAddress[pynthToRemove]; + delete pynths[currencyKey]; + + emit PynthRemoved(currencyKey, pynthToRemove); + } + + function removePynth(bytes32 currencyKey) external onlyOwner { + // Remove its contribution from the debt pool snapshot, and + // invalidate the cache to force a new snapshot. + IIssuerInternalDebtCache cache = debtCache(); + cache.updateCachedPynthDebtWithRate(currencyKey, 0); + cache.updateDebtCacheValidity(true); + + _removePynth(currencyKey); + } + + function removePynths(bytes32[] calldata currencyKeys) external onlyOwner { + uint numKeys = currencyKeys.length; + + // Remove their contributions from the debt pool snapshot, and + // invalidate the cache to force a new snapshot. + IIssuerInternalDebtCache cache = debtCache(); + uint[] memory zeroRates = new uint[](numKeys); + cache.updateCachedPynthDebtsWithRates(currencyKeys, zeroRates); + cache.updateDebtCacheValidity(true); + + for (uint i = 0; i < numKeys; i++) { + _removePynth(currencyKeys[i]); + } + } + + function issuePynths( + address _issuer, + bytes32 _currencyKey, + uint _issueAmount + ) external onlyPeriFinance { + _requireCurrencyKeyIsNotpUSD(_currencyKey); + + if (_currencyKey != PERI) { + uint amountToStake = _issueAmount.divideDecimalRound(getIssuanceRatio()); + + (uint initialDebtOwnership, ) = periFinanceState().issuanceData(_issuer); + // Condition of policy, user must have any amount of PERI locked before staking external token. + require(initialDebtOwnership > 0, "User does not have any debt yet"); + + exTokenStakeManager().stake(_issuer, amountToStake, _currencyKey, pUSD); + } + + (uint maxIssuable, uint existingDebt, uint totalSystemDebt, bool anyRateIsInvalid) = + _remainingIssuablePynths(_issuer); + _requireRatesNotInvalid(anyRateIsInvalid); + + uint afterDebtBalance = _issuePynths(_issuer, _issueAmount, maxIssuable, existingDebt, totalSystemDebt, false); + + // For preventing additional gas consumption by calculating debt twice, the quota checker is placed here. + _requireNotExceedsQuotaLimit(_issuer, afterDebtBalance, 0, 0, true); + } + + function issueMaxPynths(address _issuer) external onlyPeriFinance { + (uint maxIssuable, uint existingDebt, uint totalSystemDebt, bool anyRateIsInvalid) = + _remainingIssuablePynths(_issuer); + _requireRatesNotInvalid(anyRateIsInvalid); + + _issuePynths(_issuer, 0, maxIssuable, existingDebt, totalSystemDebt, true); + } + + function issuePynthsToMaxQuota(address _issuer, bytes32 _currencyKey) external onlyPeriFinance { + _requireCurrencyKeyIsNotpUSD(_currencyKey); + require(_currencyKey != PERI, "Only external token allowed to stake"); + + (uint maxIssuable, uint existingDebt, uint totalSystemDebt, bool anyRateIsInvalid) = + _remainingIssuablePynths(_issuer); + _requireRatesNotInvalid(anyRateIsInvalid); + require(existingDebt > 0, "User does not have any debt yet"); + + uint combinedStakedAmount = exTokenStakeManager().combinedStakedAmountOf(_issuer, pUSD); + (uint issueAmountToQuota, uint stakeAmountToQuota) = + _maxExternalTokenStakeAmount(_issuer, existingDebt, combinedStakedAmount, _currencyKey); + + require(issueAmountToQuota > 0 && stakeAmountToQuota > 0, "No available external token staking amount"); + + exTokenStakeManager().stake(_issuer, stakeAmountToQuota, _currencyKey, pUSD); + + // maxIssuable should be increased for increased collateral + maxIssuable = maxIssuable.add(issueAmountToQuota); + + uint afterDebtBalance = _issuePynths(_issuer, issueAmountToQuota, maxIssuable, existingDebt, totalSystemDebt, false); + + // For preventing additional gas consumption by calculating debt twice, the quota checker is placed here. + _requireNotExceedsQuotaLimit(_issuer, afterDebtBalance, 0, 0, true); + } + + function burnPynths( + address _from, + bytes32 _currencyKey, + uint _burnAmount + ) external onlyPeriFinance { + _requireCurrencyKeyIsNotpUSD(_currencyKey); + + uint remainingDebt = _voluntaryBurnPynths(_from, _burnAmount, false, false); + + if (_currencyKey == PERI) { + _requireNotExceedsQuotaLimit(_from, remainingDebt, 0, 0, false); + } + + if (_currencyKey != PERI) { + exTokenStakeManager().unstake(_from, _burnAmount.divideDecimalRound(getIssuanceRatio()), _currencyKey, pUSD); + } + } + + function fitToClaimable(address _from) external onlyPeriFinance { + (uint debtBalance, , bool anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_from, pUSD); + uint combinedStakedAmount = exTokenStakeManager().combinedStakedAmountOf(_from, pUSD); + + (uint periRate, bool isPeriInvalid) = exchangeRates().rateAndInvalid(PERI); + uint periCollateralToUSD = _periToUSD(_collateral(_from), periRate); + + _requireRatesNotInvalid(anyRateIsInvalid || isPeriInvalid); + + (uint burnAmount, uint amountToUnstake) = + _amountsToFitClaimable(debtBalance, combinedStakedAmount, periCollateralToUSD); + + _voluntaryBurnPynths(_from, burnAmount, true, false); + + exTokenStakeManager().unstakeMultipleTokens(_from, amountToUnstake, pUSD); + } + + function exit(address _from) external onlyPeriFinance { + _voluntaryBurnPynths(_from, 0, true, true); + + bytes32[] memory tokenList = exTokenStakeManager().getTokenList(); + for (uint i = 0; i < tokenList.length; i++) { + uint stakedAmount = exTokenStakeManager().stakedAmountOf(_from, tokenList[i], tokenList[i]); + + if (stakedAmount == 0) { + continue; + } + + exTokenStakeManager().unstake(_from, stakedAmount, tokenList[i], tokenList[i]); + } + } + + function liquidateDelinquentAccount( + address account, + uint pusdAmount, + address liquidator + ) external onlyPeriFinance returns (uint totalRedeemed, uint amountToLiquidate) { + // Ensure waitingPeriod and pUSD balance is settled as burning impacts the size of debt pool + require(!exchanger().hasWaitingPeriodOrSettlementOwing(liquidator, pUSD), "pUSD needs to be settled"); + + // Check account is liquidation open + require(liquidations().isOpenForLiquidation(account), "Account not open for liquidation"); + + // require liquidator has enough pUSD + require(IERC20(address(pynths[pUSD])).balanceOf(liquidator) >= pusdAmount, "Not enough pUSD"); + + uint liquidationPenalty = liquidations().liquidationPenalty(); + + // What is their debt in pUSD? + (uint debtBalance, uint totalDebtIssued, bool anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(account, pUSD); + (uint periRate, bool periRateInvalid) = exchangeRates().rateAndInvalid(PERI); + _requireRatesNotInvalid(anyRateIsInvalid || periRateInvalid); + + uint collateralForAccount = _collateral(account); + uint amountToFixRatio = + liquidations().calculateAmountToFixCollateral(debtBalance, _periToUSD(collateralForAccount, periRate)); + + // Cap amount to liquidate to repair collateral ratio based on issuance ratio + amountToLiquidate = amountToFixRatio < pusdAmount ? amountToFixRatio : pusdAmount; + + // what's the equivalent amount of peri for the amountToLiquidate? + uint periRedeemed = _usdToPeri(amountToLiquidate, periRate); + + // Add penalty + totalRedeemed = periRedeemed.multiplyDecimal(SafeDecimalMath.unit().add(liquidationPenalty)); + + // if total PERI to redeem is greater than account's collateral + // account is under collateralised, liquidate all collateral and reduce pUSD to burn + if (totalRedeemed > collateralForAccount) { + // set totalRedeemed to all transferable collateral + totalRedeemed = collateralForAccount; + + // whats the equivalent pUSD to burn for all collateral less penalty + amountToLiquidate = _periToUSD( + collateralForAccount.divideDecimal(SafeDecimalMath.unit().add(liquidationPenalty)), + periRate + ); + } + + // burn pUSD from messageSender (liquidator) and reduce account's debt + _burnPynths(account, liquidator, amountToLiquidate, debtBalance, totalDebtIssued); + + // Remove liquidation flag if amount liquidated fixes ratio + if (amountToLiquidate == amountToFixRatio) { + // Remove liquidation + liquidations().removeAccountInLiquidation(account); + } + } + + /* ========== INTERNAL FUNCTIONS ========== */ + + function _requireRatesNotInvalid(bool anyRateIsInvalid) internal pure { + require(!anyRateIsInvalid, "A pynth or PERI rate is invalid"); + } + + function _requireCanIssueOnBehalf(address issueForAddress, address from) internal view { + require(delegateApprovals().canIssueFor(issueForAddress, from), "Not approved to act on behalf"); + } + + function _requireCanBurnOnBehalf(address burnForAddress, address from) internal view { + require(delegateApprovals().canBurnFor(burnForAddress, from), "Not approved to act on behalf"); + } + + function _requireCurrencyKeyIsNotpUSD(bytes32 _currencyKey) internal pure { + require(_currencyKey != pUSD, "pUSD is not staking coin"); + } + + function _requireNotExceedsQuotaLimit( + address _account, + uint _debtBalance, + uint _additionalpUSD, + uint _additionalExToken, + bool _isIssue + ) internal view { + uint estimatedExternalTokenQuota = + _externalTokenQuota(_account, _debtBalance, _additionalpUSD, _additionalExToken, _isIssue); + + bytes32[] memory tokenList = exTokenStakeManager().getTokenList(); + uint minDecimals = 18; + for (uint i = 0; i < tokenList.length; i++) { + uint decimals = exTokenStakeManager().getTokenDecimals(tokenList[i]); + + minDecimals = decimals < minDecimals ? decimals : minDecimals; + } + + require( + // due to the error caused by decimal difference, round down it upto minimum decimals among staking token list. + estimatedExternalTokenQuota.roundDownDecimal(uint(18).sub(minDecimals)) <= getExternalTokenQuota(), + "External token staking amount exceeds quota limit" + ); + } + + function _issuePynths( + address from, + uint amount, + uint maxIssuable, + uint existingDebt, + uint totalSystemDebt, + bool issueMax + ) internal returns (uint afterDebt) { + if (!issueMax) { + require(amount <= maxIssuable, "Amount too large"); + } else { + amount = maxIssuable; + } + + // Keep track of the debt they're about to create + _addToDebtRegister(from, amount, existingDebt, totalSystemDebt); + + // record issue timestamp + _setLastIssueEvent(from); + + // Create their pynths + pynths[pUSD].issue(from, amount); + + // Account for the issued debt in the cache + debtCache().updateCachedPynthDebtWithRate(pUSD, SafeDecimalMath.unit()); + + // Store their locked PERI amount to determine their fee % for the period + _appendAccountIssuanceRecord(from); + + afterDebt = existingDebt.add(amount); + } + + function _burnPynths( + address debtAccount, + address burnAccount, + uint amountBurnt, + uint existingDebt, + uint totalDebtIssued + ) internal returns (uint) { + // liquidation requires pUSD to be already settled / not in waiting period + + require(amountBurnt <= existingDebt, "Trying to burn more than debt"); + + // Remove liquidated debt from the ledger + _removeFromDebtRegister(debtAccount, amountBurnt, existingDebt, totalDebtIssued); + + // pynth.burn does a safe subtraction on balance (so it will revert if there are not enough pynths). + pynths[pUSD].burn(burnAccount, amountBurnt); + + // Account for the burnt debt in the cache. + debtCache().updateCachedPynthDebtWithRate(pUSD, SafeDecimalMath.unit()); + + // Store their debtRatio against a fee period to determine their fee/rewards % for the period + _appendAccountIssuanceRecord(debtAccount); + + return amountBurnt; + } + + // If burning to target, `amount` is ignored, and the correct quantity of pUSD is burnt to reach the target + // c-ratio, allowing fees to be claimed. In this case, pending settlements will be skipped as the user + // will still have debt remaining after reaching their target. + function _voluntaryBurnPynths( + address from, + uint amount, + bool burnToTarget, + bool burnMax + ) internal returns (uint remainingDebt) { + if (!burnToTarget) { + // If not burning to target, then burning requires that the minimum stake time has elapsed. + require(_canBurnPynths(from), "Minimum stake time not reached"); + // First settle anything pending into pUSD as burning or issuing impacts the size of the debt pool + (, uint refunded, uint numEntriesSettled) = exchanger().settle(from, pUSD); + if (numEntriesSettled > 0) { + amount = exchanger().calculateAmountAfterSettlement(from, pUSD, amount, refunded); + } + } + + (uint existingDebt, uint totalSystemValue, bool anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(from, pUSD); + (uint maxIssuablePynthsForAccount, bool periRateInvalid) = _maxIssuablePynths(from); + _requireRatesNotInvalid(anyRateIsInvalid || periRateInvalid); + require(existingDebt > 0, "No debt to forgive"); + + if (burnMax) { + amount = existingDebt; + } + + uint amountBurnt = _burnPynths(from, from, amount, existingDebt, totalSystemValue); + remainingDebt = existingDebt.sub(amountBurnt); + + // Check and remove liquidation if existingDebt after burning is <= maxIssuablePynths + // Issuance ratio is fixed so should remove any liquidations + if (existingDebt >= amountBurnt && remainingDebt <= maxIssuablePynthsForAccount) { + liquidations().removeAccountInLiquidation(from); + } + } + + function _setLastIssueEvent(address account) internal { + // Set the timestamp of the last issuePynths + flexibleStorage().setUIntValue( + CONTRACT_NAME, + keccak256(abi.encodePacked(LAST_ISSUE_EVENT, account)), + block.timestamp + ); + } + + function _appendAccountIssuanceRecord(address from) internal { + uint initialDebtOwnership; + uint debtEntryIndex; + (initialDebtOwnership, debtEntryIndex) = periFinanceState().issuanceData(from); + feePool().appendAccountIssuanceRecord(from, initialDebtOwnership, debtEntryIndex); + } + + function _addToDebtRegister( + address from, + uint amount, + uint existingDebt, + uint totalDebtIssued + ) internal { + IPeriFinanceState state = periFinanceState(); + + // What will the new total be including the new value? + uint newTotalDebtIssued = amount.add(totalDebtIssued); + + // What is their percentage (as a high precision int) of the total debt? + uint debtPercentage = amount.divideDecimalRoundPrecise(newTotalDebtIssued); + + // And what effect does this percentage change have on the global debt holding of other issuers? + // The delta specifically needs to not take into account any existing debt as it's already + // accounted for in the delta from when they issued previously. + // The delta is a high precision integer. + uint delta = SafeDecimalMath.preciseUnit().sub(debtPercentage); + + // And what does their debt ownership look like including this previous stake? + if (existingDebt > 0) { + debtPercentage = amount.add(existingDebt).divideDecimalRoundPrecise(newTotalDebtIssued); + } else { + // If they have no debt, they're a new issuer; record this. + state.incrementTotalIssuerCount(); + } + + // Save the debt entry parameters + state.setCurrentIssuanceData(from, debtPercentage); + + // And if we're the first, push 1 as there was no effect to any other holders, otherwise push + // the change for the rest of the debt holders. The debt ledger holds high precision integers. + if (state.debtLedgerLength() > 0 && state.lastDebtLedgerEntry() != 0) { + state.appendDebtLedgerValue(state.lastDebtLedgerEntry().multiplyDecimalRoundPrecise(delta)); + } else { + state.appendDebtLedgerValue(SafeDecimalMath.preciseUnit()); + } + } + + function _removeFromDebtRegister( + address from, + uint debtToRemove, + uint existingDebt, + uint totalDebtIssued + ) internal { + IPeriFinanceState state = periFinanceState(); + + // What will the new total after taking out the withdrawn amount + uint newTotalDebtIssued = totalDebtIssued.sub(debtToRemove); + + uint delta = 0; + + // What will the debt delta be if there is any debt left? + // Set delta to 0 if no more debt left in system after user + if (newTotalDebtIssued > 0) { + // What is the percentage of the withdrawn debt (as a high precision int) of the total debt after? + uint debtPercentage = debtToRemove.divideDecimalRoundPrecise(newTotalDebtIssued); + + // And what effect does this percentage change have on the global debt holding of other issuers? + // The delta specifically needs to not take into account any existing debt as it's already + // accounted for in the delta from when they issued previously. + delta = SafeDecimalMath.preciseUnit().add(debtPercentage); + } + + // Are they exiting the system, or are they just decreasing their debt position? + if (debtToRemove == existingDebt) { + state.setCurrentIssuanceData(from, 0); + state.decrementTotalIssuerCount(); + } else { + // What percentage of the debt will they be left with? + uint newDebt = existingDebt.sub(debtToRemove); + uint newDebtPercentage = newDebt.divideDecimalRoundPrecise(newTotalDebtIssued); + + // Store the debt percentage and debt ledger as high precision integers + state.setCurrentIssuanceData(from, newDebtPercentage); + } + + // Update our cumulative ledger. This is also a high precision integer. + state.appendDebtLedgerValue(state.lastDebtLedgerEntry().multiplyDecimalRoundPrecise(delta)); + } + + /* ========== MODIFIERS ========== */ + + function _onlyPeriFinance() internal view { + require(msg.sender == address(periFinance()), "Issuer: Only the periFinance contract can perform this action"); + } + + modifier onlyPeriFinance() { + _onlyPeriFinance(); // Use an internal function to save code size. + _; + } + + /* ========== EVENTS ========== */ + + event PynthAdded(bytes32 currencyKey, address pynth); + event PynthRemoved(bytes32 currencyKey, address pynth); +} \ 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 0000000000..2a3d2f0291 --- /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/BConst.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BConst.sol new file mode 100644 index 0000000000..7f2b3f25d3 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BConst.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.6.0; + + +/************************************************************************************************ +Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BConst.sol + +This source code has been modified from the original, which was copied from the github repository +at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. + +Subject to the GPL-3.0 license +*************************************************************************************************/ + + +contract BConst { + uint256 public constant VERSION_NUMBER = 1; + +/* --- Weight Updates --- */ + + // Minimum time passed between each weight update for a token. + uint256 internal constant WEIGHT_UPDATE_DELAY = 30 minutes; + + // Maximum percent by which a weight can adjust at a time + // relative to the current weight. + // The number of iterations needed to move from weight A to weight B is the floor of: + // (A > B): (ln(A) - ln(B)) / ln(1.01) + // (B > A): (ln(A) - ln(B)) / ln(0.99) + uint256 internal constant WEIGHT_CHANGE_PCT = BONE/100; + + uint256 internal constant BONE = 10**18; + + uint256 internal constant MIN_BOUND_TOKENS = 2; + uint256 internal constant MAX_BOUND_TOKENS = 10; + + // Minimum swap fee. + uint256 internal constant MIN_FEE = BONE / 10**6; + // Maximum swap or exit fee. + uint256 internal constant MAX_FEE = BONE / 10; + // Actual exit fee. + uint256 internal constant EXIT_FEE = 5e15; + + // Default total of all desired weights. Can differ by up to BONE. + uint256 internal constant DEFAULT_TOTAL_WEIGHT = BONE * 25; + // Minimum weight for any token (1/100). + uint256 internal constant MIN_WEIGHT = BONE / 4; + uint256 internal constant MAX_WEIGHT = BONE * 25; + // Maximum total weight. + uint256 internal constant MAX_TOTAL_WEIGHT = BONE * 27; + // Minimum balance for a token (only applied at initialization) + uint256 internal constant MIN_BALANCE = BONE / 10**12; + // Initial pool tokens + uint256 internal constant INIT_POOL_SUPPLY = BONE * 100; + + uint256 internal constant MIN_BPOW_BASE = 1 wei; + uint256 internal constant MAX_BPOW_BASE = (2 * BONE) - 1 wei; + uint256 internal constant BPOW_PRECISION = BONE / 10**10; + + // Maximum ratio of input tokens to balance for swaps. + uint256 internal constant MAX_IN_RATIO = BONE / 2; + // Maximum ratio of output tokens to balance for swaps. + uint256 internal constant MAX_OUT_RATIO = (BONE / 3) + 1 wei; +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BMath.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BMath.sol new file mode 100644 index 0000000000..47593291a2 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BMath.sol @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.6.0; + +import "./BNum.sol"; + + +/************************************************************************************************ +Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BMath.sol + +This source code has been modified from the original, which was copied from the github repository +at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. + +Subject to the GPL-3.0 license +*************************************************************************************************/ + + +contract BMath is BConst, BNum { + /********************************************************************************************** + // calcSpotPrice // + // sP = spotPrice // + // bI = tokenBalanceIn ( bI / wI ) 1 // + // bO = tokenBalanceOut sP = ----------- * ---------- // + // wI = tokenWeightIn ( bO / wO ) ( 1 - sF ) // + // wO = tokenWeightOut // + // sF = swapFee // + **********************************************************************************************/ + function calcSpotPrice( + uint256 tokenBalanceIn, + uint256 tokenWeightIn, + uint256 tokenBalanceOut, + uint256 tokenWeightOut, + uint256 swapFee + ) internal pure returns (uint256 spotPrice) { + uint256 numer = bdiv(tokenBalanceIn, tokenWeightIn); + uint256 denom = bdiv(tokenBalanceOut, tokenWeightOut); + uint256 ratio = bdiv(numer, denom); + uint256 scale = bdiv(BONE, bsub(BONE, swapFee)); + return (spotPrice = bmul(ratio, scale)); + } + + /********************************************************************************************** + // calcOutGivenIn // + // aO = tokenAmountOut // + // bO = tokenBalanceOut // + // bI = tokenBalanceIn / / bI \ (wI / wO) \ // + // aI = tokenAmountIn aO = bO * | 1 - | -------------------------- | ^ | // + // wI = tokenWeightIn \ \ ( bI + ( aI * ( 1 - sF )) / / // + // wO = tokenWeightOut // + // sF = swapFee // + **********************************************************************************************/ + function calcOutGivenIn( + uint256 tokenBalanceIn, + uint256 tokenWeightIn, + uint256 tokenBalanceOut, + uint256 tokenWeightOut, + uint256 tokenAmountIn, + uint256 swapFee + ) internal pure returns (uint256 tokenAmountOut) { + uint256 weightRatio = bdiv(tokenWeightIn, tokenWeightOut); + uint256 adjustedIn = bsub(BONE, swapFee); + adjustedIn = bmul(tokenAmountIn, adjustedIn); + uint256 y = bdiv(tokenBalanceIn, badd(tokenBalanceIn, adjustedIn)); + uint256 foo = bpow(y, weightRatio); + uint256 bar = bsub(BONE, foo); + tokenAmountOut = bmul(tokenBalanceOut, bar); + return tokenAmountOut; + } + + /********************************************************************************************** + // calcInGivenOut // + // aI = tokenAmountIn // + // bO = tokenBalanceOut / / bO \ (wO / wI) \ // + // bI = tokenBalanceIn bI * | | ------------ | ^ - 1 | // + // aO = tokenAmountOut aI = \ \ ( bO - aO ) / / // + // wI = tokenWeightIn -------------------------------------------- // + // wO = tokenWeightOut ( 1 - sF ) // + // sF = swapFee // + **********************************************************************************************/ + function calcInGivenOut( + uint256 tokenBalanceIn, + uint256 tokenWeightIn, + uint256 tokenBalanceOut, + uint256 tokenWeightOut, + uint256 tokenAmountOut, + uint256 swapFee + ) internal pure returns (uint256 tokenAmountIn) { + uint256 weightRatio = bdiv(tokenWeightOut, tokenWeightIn); + uint256 diff = bsub(tokenBalanceOut, tokenAmountOut); + uint256 y = bdiv(tokenBalanceOut, diff); + uint256 foo = bpow(y, weightRatio); + foo = bsub(foo, BONE); + tokenAmountIn = bsub(BONE, swapFee); + tokenAmountIn = bdiv(bmul(tokenBalanceIn, foo), tokenAmountIn); + return tokenAmountIn; + } + + /********************************************************************************************** + // calcPoolOutGivenSingleIn // + // pAo = poolAmountOut / \ // + // tAi = tokenAmountIn /// / // wI \ \\ \ wI \ // + // wI = tokenWeightIn //| tAi *| 1 - || 1 - -- | * sF || + tBi \ -- \ // + // tW = totalWeight pAo=|| \ \ \\ tW / // | ^ tW | * pS - pS // + // tBi = tokenBalanceIn \\ ------------------------------------- / / // + // pS = poolSupply \\ tBi / / // + // sF = swapFee \ / // + **********************************************************************************************/ + function calcPoolOutGivenSingleIn( + uint256 tokenBalanceIn, + uint256 tokenWeightIn, + uint256 poolSupply, + uint256 totalWeight, + uint256 tokenAmountIn, + uint256 swapFee + ) internal pure returns (uint256 poolAmountOut) { + // Charge the trading fee for the proportion of tokenAi + /// which is implicitly traded to the other pool tokens. + // That proportion is (1- weightTokenIn) + // tokenAiAfterFee = tAi * (1 - (1-weightTi) * poolFee); + uint256 normalizedWeight = bdiv(tokenWeightIn, totalWeight); + uint256 zaz = bmul(bsub(BONE, normalizedWeight), swapFee); + uint256 tokenAmountInAfterFee = bmul(tokenAmountIn, bsub(BONE, zaz)); + + uint256 newTokenBalanceIn = badd(tokenBalanceIn, tokenAmountInAfterFee); + uint256 tokenInRatio = bdiv(newTokenBalanceIn, tokenBalanceIn); + + // uint newPoolSupply = (ratioTi ^ weightTi) * poolSupply; + uint256 poolRatio = bpow(tokenInRatio, normalizedWeight); + uint256 newPoolSupply = bmul(poolRatio, poolSupply); + poolAmountOut = bsub(newPoolSupply, poolSupply); + return poolAmountOut; + } + + /********************************************************************************************** + // calcSingleInGivenPoolOut // + // tAi = tokenAmountIn //(pS + pAo)\ / 1 \\ // + // pS = poolSupply || --------- | ^ | --------- || * bI - bI // + // pAo = poolAmountOut \\ pS / \(wI / tW)// // + // bI = balanceIn tAi = -------------------------------------------- // + // wI = weightIn / wI \ // + // tW = totalWeight | 1 - ---- | * sF // + // sF = swapFee \ tW / // + **********************************************************************************************/ + function calcSingleInGivenPoolOut( + uint256 tokenBalanceIn, + uint256 tokenWeightIn, + uint256 poolSupply, + uint256 totalWeight, + uint256 poolAmountOut, + uint256 swapFee + ) internal pure returns (uint256 tokenAmountIn) { + uint256 normalizedWeight = bdiv(tokenWeightIn, totalWeight); + uint256 newPoolSupply = badd(poolSupply, poolAmountOut); + uint256 poolRatio = bdiv(newPoolSupply, poolSupply); + + //uint newBalTi = poolRatio^(1/weightTi) * balTi; + uint256 boo = bdiv(BONE, normalizedWeight); + uint256 tokenInRatio = bpow(poolRatio, boo); + uint256 newTokenBalanceIn = bmul(tokenInRatio, tokenBalanceIn); + uint256 tokenAmountInAfterFee = bsub(newTokenBalanceIn, tokenBalanceIn); + // Do reverse order of fees charged in joinswap_ExternAmountIn, this way + // ``` pAo == joinswap_ExternAmountIn(Ti, joinswap_PoolAmountOut(pAo, Ti)) ``` + //uint tAi = tAiAfterFee / (1 - (1-weightTi) * swapFee) ; + uint256 zar = bmul(bsub(BONE, normalizedWeight), swapFee); + tokenAmountIn = bdiv(tokenAmountInAfterFee, bsub(BONE, zar)); + return tokenAmountIn; + } + + /********************************************************************************************** + // calcSingleOutGivenPoolIn // + // tAo = tokenAmountOut / / \\ // + // bO = tokenBalanceOut / // pS - (pAi * (1 - eF)) \ / 1 \ \\ // + // pAi = poolAmountIn | bO - || ----------------------- | ^ | --------- | * b0 || // + // ps = poolSupply \ \\ pS / \(wO / tW)/ // // + // wI = tokenWeightIn tAo = \ \ // // + // tW = totalWeight / / wO \ \ // + // sF = swapFee * | 1 - | 1 - ---- | * sF | // + // eF = exitFee \ \ tW / / // + **********************************************************************************************/ + function calcSingleOutGivenPoolIn( + uint256 tokenBalanceOut, + uint256 tokenWeightOut, + uint256 poolSupply, + uint256 totalWeight, + uint256 poolAmountIn, + uint256 swapFee + ) internal pure returns (uint256 tokenAmountOut) { + uint256 normalizedWeight = bdiv(tokenWeightOut, totalWeight); + // charge exit fee on the pool token side + // pAiAfterExitFee = pAi*(1-exitFee) + uint256 poolAmountInAfterExitFee = bmul(poolAmountIn, bsub(BONE, EXIT_FEE)); + uint256 newPoolSupply = bsub(poolSupply, poolAmountInAfterExitFee); + uint256 poolRatio = bdiv(newPoolSupply, poolSupply); + + // newBalTo = poolRatio^(1/weightTo) * balTo; + uint256 tokenOutRatio = bpow(poolRatio, bdiv(BONE, normalizedWeight)); + uint256 newTokenBalanceOut = bmul(tokenOutRatio, tokenBalanceOut); + + uint256 tokenAmountOutBeforeSwapFee = bsub( + tokenBalanceOut, + newTokenBalanceOut + ); + + // charge swap fee on the output token side + //uint tAo = tAoBeforeSwapFee * (1 - (1-weightTo) * swapFee) + uint256 zaz = bmul(bsub(BONE, normalizedWeight), swapFee); + tokenAmountOut = bmul(tokenAmountOutBeforeSwapFee, bsub(BONE, zaz)); + return tokenAmountOut; + } + + /********************************************************************************************** + // calcPoolInGivenSingleOut // + // pAi = poolAmountIn // / tAo \\ / wO \ \ // + // bO = tokenBalanceOut // | bO - -------------------------- |\ | ---- | \ // + // tAo = tokenAmountOut pS - || \ 1 - ((1 - (tO / tW)) * sF)/ | ^ \ tW / * pS | // + // ps = poolSupply \\ -----------------------------------/ / // + // wO = tokenWeightOut pAi = \\ bO / / // + // tW = totalWeight ------------------------------------------------------------- // + // sF = swapFee ( 1 - eF ) // + // eF = exitFee // + **********************************************************************************************/ + function calcPoolInGivenSingleOut( + uint256 tokenBalanceOut, + uint256 tokenWeightOut, + uint256 poolSupply, + uint256 totalWeight, + uint256 tokenAmountOut, + uint256 swapFee + ) internal pure returns (uint256 poolAmountIn) { + // charge swap fee on the output token side + uint256 normalizedWeight = bdiv(tokenWeightOut, totalWeight); + //uint tAoBeforeSwapFee = tAo / (1 - (1-weightTo) * swapFee) ; + uint256 zoo = bsub(BONE, normalizedWeight); + uint256 zar = bmul(zoo, swapFee); + uint256 tokenAmountOutBeforeSwapFee = bdiv(tokenAmountOut, bsub(BONE, zar)); + + uint256 newTokenBalanceOut = bsub( + tokenBalanceOut, + tokenAmountOutBeforeSwapFee + ); + uint256 tokenOutRatio = bdiv(newTokenBalanceOut, tokenBalanceOut); + + //uint newPoolSupply = (ratioTo ^ weightTo) * poolSupply; + uint256 poolRatio = bpow(tokenOutRatio, normalizedWeight); + uint256 newPoolSupply = bmul(poolRatio, poolSupply); + uint256 poolAmountInAfterExitFee = bsub(poolSupply, newPoolSupply); + + // charge exit fee on the pool token side + // pAi = pAiAfterExitFee/(1-exitFee) + poolAmountIn = bdiv(poolAmountInAfterExitFee, bsub(BONE, EXIT_FEE)); + return poolAmountIn; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BNum.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BNum.sol new file mode 100644 index 0000000000..8dfe7689d0 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BNum.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.6.0; + +import "./BConst.sol"; + + +/************************************************************************************************ +Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BNum.sol + +This source code has been modified from the original, which was copied from the github repository +at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. + +Subject to the GPL-3.0 license +*************************************************************************************************/ + + +contract BNum is BConst { + function btoi(uint256 a) internal pure returns (uint256) { + return a / BONE; + } + + function bfloor(uint256 a) internal pure returns (uint256) { + return btoi(a) * BONE; + } + + function badd(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "ERR_ADD_OVERFLOW"); + return c; + } + + function bsub(uint256 a, uint256 b) internal pure returns (uint256) { + (uint256 c, bool flag) = bsubSign(a, b); + require(!flag, "ERR_SUB_UNDERFLOW"); + return c; + } + + function bsubSign(uint256 a, uint256 b) + internal + pure + returns (uint256, bool) + { + if (a >= b) { + return (a - b, false); + } else { + return (b - a, true); + } + } + + function bmul(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c0 = a * b; + require(a == 0 || c0 / a == b, "ERR_MUL_OVERFLOW"); + uint256 c1 = c0 + (BONE / 2); + require(c1 >= c0, "ERR_MUL_OVERFLOW"); + uint256 c2 = c1 / BONE; + return c2; + } + + function bdiv(uint256 a, uint256 b) internal pure returns (uint256) { + require(b != 0, "ERR_DIV_ZERO"); + uint256 c0 = a * BONE; + require(a == 0 || c0 / a == BONE, "ERR_DIV_INTERNAL"); // bmul overflow + uint256 c1 = c0 + (b / 2); + require(c1 >= c0, "ERR_DIV_INTERNAL"); // badd require + uint256 c2 = c1 / b; + return c2; + } + + // DSMath.wpow + function bpowi(uint256 a, uint256 n) internal pure returns (uint256) { + uint256 z = n % 2 != 0 ? a : BONE; + + for (n /= 2; n != 0; n /= 2) { + a = bmul(a, a); + + if (n % 2 != 0) { + z = bmul(z, a); + } + } + return z; + } + + // Compute b^(e.w) by splitting it into (b^e)*(b^0.w). + // Use `bpowi` for `b^e` and `bpowK` for k iterations + // of approximation of b^0.w + function bpow(uint256 base, uint256 exp) internal pure returns (uint256) { + require(base >= MIN_BPOW_BASE, "ERR_BPOW_BASE_TOO_LOW"); + require(base <= MAX_BPOW_BASE, "ERR_BPOW_BASE_TOO_HIGH"); + + uint256 whole = bfloor(exp); + uint256 remain = bsub(exp, whole); + + uint256 wholePow = bpowi(base, btoi(whole)); + + if (remain == 0) { + return wholePow; + } + + uint256 partialResult = bpowApprox(base, remain, BPOW_PRECISION); + return bmul(wholePow, partialResult); + } + + function bpowApprox( + uint256 base, + uint256 exp, + uint256 precision + ) internal pure returns (uint256) { + // term 0: + uint256 a = exp; + (uint256 x, bool xneg) = bsubSign(base, BONE); + uint256 term = BONE; + uint256 sum = term; + bool negative = false; + + // term(k) = numer / denom + // = (product(a - i - 1, i=1-->k) * x^k) / (k!) + // each iteration, multiply previous term by (a-(k-1)) * x / k + // continue until term is less than precision + for (uint256 i = 1; term >= precision; i++) { + uint256 bigK = i * BONE; + (uint256 c, bool cneg) = bsubSign(a, bsub(bigK, BONE)); + term = bmul(term, bmul(c, x)); + term = bdiv(term, bigK); + if (term == 0) break; + + if (xneg) negative = !negative; + if (cneg) negative = !negative; + if (negative) { + sum = bsub(sum, term); + } else { + sum = badd(sum, term); + } + } + + return sum; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BToken.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BToken.sol new file mode 100644 index 0000000000..1053a7f002 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BToken.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.6.0; + +import "./BNum.sol"; + + +/************************************************************************************************ +Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BToken.sol + +This source code has been modified from the original, which was copied from the github repository +at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. + +Subject to the GPL-3.0 license +*************************************************************************************************/ + + +// Highly opinionated token implementation +interface IERC20 { + event Approval(address indexed src, address indexed dst, uint256 amt); + event Transfer(address indexed src, address indexed dst, uint256 amt); + + function name() external view returns (string memory); + + function symbol() external view returns (string memory); + + function decimals() external view returns (uint8); + + function totalSupply() external view returns (uint256); + + function balanceOf(address whom) external view returns (uint256); + + function allowance(address src, address dst) external view returns (uint256); + + function approve(address dst, uint256 amt) external returns (bool); + + function transfer(address dst, uint256 amt) external returns (bool); + + function transferFrom( + address src, + address dst, + uint256 amt + ) external returns (bool); +} + + +contract BTokenBase is BNum { + mapping(address => uint256) internal _balance; + mapping(address => mapping(address => uint256)) internal _allowance; + uint256 internal _totalSupply; + + event Approval(address indexed src, address indexed dst, uint256 amt); + event Transfer(address indexed src, address indexed dst, uint256 amt); + + function _mint(uint256 amt) internal { + _balance[address(this)] = badd(_balance[address(this)], amt); + _totalSupply = badd(_totalSupply, amt); + emit Transfer(address(0), address(this), amt); + } + + function _burn(uint256 amt) internal { + require(_balance[address(this)] >= amt, "ERR_INSUFFICIENT_BAL"); + _balance[address(this)] = bsub(_balance[address(this)], amt); + _totalSupply = bsub(_totalSupply, amt); + emit Transfer(address(this), address(0), amt); + } + + function _move( + address src, + address dst, + uint256 amt + ) internal { + require(_balance[src] >= amt, "ERR_INSUFFICIENT_BAL"); + _balance[src] = bsub(_balance[src], amt); + _balance[dst] = badd(_balance[dst], amt); + emit Transfer(src, dst, amt); + } + + function _push(address to, uint256 amt) internal { + _move(address(this), to, amt); + } + + function _pull(address from, uint256 amt) internal { + _move(from, address(this), amt); + } +} + + +contract BToken is BTokenBase, IERC20 { + uint8 private constant DECIMALS = 18; + string private _name; + string private _symbol; + + function _initializeToken(string memory name, string memory symbol) internal { + require( + bytes(_name).length == 0 && + bytes(name).length != 0 && + bytes(symbol).length != 0, + "ERR_BTOKEN_INITIALIZED" + ); + _name = name; + _symbol = symbol; + } + + function name() + external + override + view + returns (string memory) + { + return _name; + } + + function symbol() + external + override + view + returns (string memory) + { + return _symbol; + } + + function decimals() + external + override + view + returns (uint8) + { + return DECIMALS; + } + + function allowance(address src, address dst) + external + override + view + returns (uint256) + { + return _allowance[src][dst]; + } + + function balanceOf(address whom) external override view returns (uint256) { + return _balance[whom]; + } + + function totalSupply() public override view returns (uint256) { + return _totalSupply; + } + + function approve(address dst, uint256 amt) external override returns (bool) { + _allowance[msg.sender][dst] = amt; + emit Approval(msg.sender, dst, amt); + return true; + } + + function increaseApproval(address dst, uint256 amt) external returns (bool) { + _allowance[msg.sender][dst] = badd(_allowance[msg.sender][dst], amt); + emit Approval(msg.sender, dst, _allowance[msg.sender][dst]); + return true; + } + + function decreaseApproval(address dst, uint256 amt) external returns (bool) { + uint256 oldValue = _allowance[msg.sender][dst]; + if (amt > oldValue) { + _allowance[msg.sender][dst] = 0; + } else { + _allowance[msg.sender][dst] = bsub(oldValue, amt); + } + emit Approval(msg.sender, dst, _allowance[msg.sender][dst]); + return true; + } + + function transfer(address dst, uint256 amt) external override returns (bool) { + _move(msg.sender, dst, amt); + return true; + } + + function transferFrom( + address src, + address dst, + uint256 amt + ) external override returns (bool) { + require( + msg.sender == src || amt <= _allowance[src][msg.sender], + "ERR_BTOKEN_BAD_CALLER" + ); + _move(src, dst, amt); + if (msg.sender != src && _allowance[src][msg.sender] != uint256(-1)) { + _allowance[src][msg.sender] = bsub(_allowance[src][msg.sender], amt); + emit Approval(msg.sender, dst, _allowance[src][msg.sender]); + } + return true; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BeefyVault.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BeefyVault.sol new file mode 100644 index 0000000000..d855711d9c --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BeefyVault.sol @@ -0,0 +1,941 @@ +/** + *Submitted for verification at BscScan.com on 2020-10-27 +*/ + +// SPDX-License-Identifier: MIT +// File: @openzeppelin/contracts/GSN/Context.sol + + +pragma solidity ^0.6.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/token/ERC20/IERC20.sol + + +pragma solidity ^0.6.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; + +/** + * @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; + } +} + +// File: @openzeppelin/contracts/utils/Address.sol + + +pragma solidity ^0.6.2; + +/** + * @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); + } + } + } +} + +// File: @openzeppelin/contracts/token/ERC20/ERC20.sol + + +pragma solidity ^0.6.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 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 { + using SafeMath for uint256; + using Address for address; + + mapping (address => uint256) private _balances; + + mapping (address => mapping (address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + uint8 private _decimals; + + /** + * @dev Sets the values for {name} and {symbol}, initializes {decimals} with + * a default value of 18. + * + * To select a different value for {decimals}, use {_setupDecimals}. + * + * All three of these values are immutable: they can only be set once during + * construction. + */ + constructor (string memory name, string memory symbol) public { + _name = name; + _symbol = symbol; + _decimals = 18; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view 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 {_setupDecimals} is + * called. + * + * 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 returns (uint8) { + return _decimals; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view 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); + _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); + 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].add(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) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); + 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); + + _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); + _balances[recipient] = _balances[recipient].add(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 = _totalSupply.add(amount); + _balances[account] = _balances[account].add(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); + + _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); + _totalSupply = _totalSupply.sub(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 Sets {decimals} to a value other than the default one of 18. + * + * WARNING: This function should only be called from the constructor. Most + * applications that interact with token contracts will not expect + * {decimals} to ever change, and may work incorrectly if it does. + */ + function _setupDecimals(uint8 decimals_) internal { + _decimals = decimals_; + } + + /** + * @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: @openzeppelin/contracts/token/ERC20/SafeERC20.sol + + +pragma solidity ^0.6.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/BIFI/interfaces/beefy/IStrategy.sol + + +pragma solidity ^0.6.0; + +interface IStrategy { + function want() external view returns (address); + function deposit() external; + function withdraw(uint256) external; + function balanceOf() external view returns (uint256); +} + +// File: contracts/BIFI/vaults/BeefyVault.sol + + +pragma solidity ^0.6.0; + + + + + + +/** + * @dev Implementation of a vault to deposit funds for yield optimizing. + * This is the contract that receives funds and that users interface with. + * The yield optimizing strategy itself is implemented in a separate 'Strategy.sol' contract. + */ +contract BeefyVault is ERC20 { + using SafeERC20 for IERC20; + using Address for address; + using SafeMath for uint256; + + IERC20 public token; + address public strategy; + + /** + * @dev Sets the value of {token} to the token that the vault will + * hold as underlying value. It initializes the vault's own 'moo' token. + * This token is minted when someone does a deposit. It is burned in order + * to withdraw the corresponding portion of the underlying assets. + * @param _token the token to maximize. + * @param _strategy the address of the strategy. + * @param _name the name of the vault token. + * @param _symbol the symbol of the vault token. + */ + constructor (address _token, address _strategy, string memory _name, string memory _symbol) public ERC20( + string(abi.encodePacked(_name)), + string(abi.encodePacked(_symbol)) + ) { + token = IERC20(_token); + strategy = _strategy; + } + + /** + * @dev It calculates the total underlying value of {token} held by the system. + * It takes into account the vault contract balance, the strategy contract balance + * and the balance deployed in other contracts as part of the strategy. + */ + function balance() public view returns (uint) { + return token.balanceOf(address(this)) + .add(IStrategy(strategy).balanceOf()); + } + + /** + * @dev Custom logic in here for how much the vault allows to be borrowed. + * We return 100% of tokens for now. Under certain conditions we might + * want to keep some of the system funds at hand in the vault, instead + * of putting them to work. + */ + function available() public view returns (uint) { + return token.balanceOf(address(this)); + } + + /** + * @dev Function for various UIs to display the current value of one of our yield tokens. + * Returns an uint with 18 decimals of how much underlying asset one vault share represents. + */ + function getPricePerFullShare() public view returns (uint) { + return balance().mul(1e18).div(totalSupply()); + } + + /** + * @dev A helper function to call deposit() with all the sender's funds. + */ + function depositAll() external { + deposit(token.balanceOf(msg.sender)); + } + + /** + * @dev The entrypoint of funds into the system. People deposit with this function + * into the vault. The vault is then in charge of sending funds into the strategy. + */ + function deposit(uint _amount) public { + uint _pool = balance(); + uint _before = token.balanceOf(address(this)); + token.safeTransferFrom(msg.sender, address(this), _amount); + uint _after = token.balanceOf(address(this)); + _amount = _after.sub(_before); // Additional check for deflationary tokens + uint shares = 0; + if (totalSupply() == 0) { + shares = _amount; + } else { + shares = (_amount.mul(totalSupply())).div(_pool); + } + _mint(msg.sender, shares); + + earn(); + } + + /** + * @dev Function to send funds into the strategy and put them to work. It's primarily called + * by the vault's deposit() function. + */ + function earn() public { + uint _bal = available(); + token.safeTransfer(strategy, _bal); + IStrategy(strategy).deposit(); + } + + /** + * @dev A helper function to call withdraw() with all the sender's funds. + */ + function withdrawAll() external { + withdraw(balanceOf(msg.sender)); + } + + /** + * @dev Function to exit the system. The vault will withdraw the required tokens + * from the strategy and pay up the token holder. A proportional number of IOU + * tokens are burned in the process. + */ + function withdraw(uint _shares) public { + uint _withdraw = (balance().mul(_shares)).div(totalSupply()); + _burn(msg.sender, _shares); + + uint _before = token.balanceOf(address(this)); + IStrategy(strategy).withdraw(_withdraw); + uint _after = token.balanceOf(address(this)); + uint _diff = _after.sub(_before); + + token.safeTransfer(msg.sender, _diff); + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/ICompLikeToken.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/ICompLikeToken.sol new file mode 100644 index 0000000000..f98b034d68 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/ICompLikeToken.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + + +interface ICompLikeToken { + function delegate(address delegatee) external; +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/IIndexPool.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/IIndexPool.sol new file mode 100644 index 0000000000..3a499c389b --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/IIndexPool.sol @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.6.0; +pragma experimental ABIEncoderV2; + + +interface IIndexPool { + /** + * @dev Token record data structure + * @param bound is token bound to pool + * @param ready has token been initialized + * @param lastDenormUpdate timestamp of last denorm change + * @param denorm denormalized weight + * @param desiredDenorm desired denormalized weight (used for incremental changes) + * @param index index of address in tokens array + * @param balance token balance + */ + struct Record { + bool bound; + bool ready; + uint40 lastDenormUpdate; + uint96 denorm; + uint96 desiredDenorm; + uint8 index; + uint256 balance; + } + +/* ========== EVENTS ========== */ + + /** @dev Emitted when tokens are swapped. */ + event LOG_SWAP( + address indexed caller, + address indexed tokenIn, + address indexed tokenOut, + uint256 tokenAmountIn, + uint256 tokenAmountOut + ); + + /** @dev Emitted when underlying tokens are deposited for pool tokens. */ + event LOG_JOIN( + address indexed caller, + address indexed tokenIn, + uint256 tokenAmountIn + ); + + /** @dev Emitted when pool tokens are burned for underlying. */ + event LOG_EXIT( + address indexed caller, + address indexed tokenOut, + uint256 tokenAmountOut + ); + + /** @dev Emitted when a token's weight updates. */ + event LOG_DENORM_UPDATED(address indexed token, uint256 newDenorm); + + /** @dev Emitted when a token's desired weight is set. */ + event LOG_DESIRED_DENORM_SET(address indexed token, uint256 desiredDenorm); + + /** @dev Emitted when a token is unbound from the pool. */ + event LOG_TOKEN_REMOVED(address token); + + /** @dev Emitted when a token is unbound from the pool. */ + event LOG_TOKEN_ADDED( + address indexed token, + uint256 desiredDenorm, + uint256 minimumBalance + ); + + /** @dev Emitted when a token's minimum balance is updated. */ + event LOG_MINIMUM_BALANCE_UPDATED(address token, uint256 minimumBalance); + + /** @dev Emitted when a token reaches its minimum balance. */ + event LOG_TOKEN_READY(address indexed token); + + /** @dev Emitted when public trades are enabled. */ + event LOG_PUBLIC_SWAP_ENABLED(); + + /** @dev Emitted when the swap fee is updated. */ + event LOG_SWAP_FEE_UPDATED(uint256 swapFee); + + /** @dev Emitted when exit fee recipient is updated. */ + event LOG_EXIT_FEE_RECIPIENT_UPDATED(address exitFeeRecipient); + + /** @dev Emitted when controller is updated. */ + event LOG_CONTROLLER_UPDATED(address exitFeeRecipient); + + function configure( + address controller, + string calldata name, + string calldata symbol, + address exitFeeRecipient + ) external; + + function initialize( + address[] calldata tokens, + uint256[] calldata balances, + uint96[] calldata denorms, + address tokenProvider, + address unbindHandler + ) external; + + function setSwapFee(uint256 swapFee) external; + + function delegateCompLikeToken(address token, address delegatee) external; + + function setExitFeeRecipient(address exitFeeRecipient) external; + + function setController(address controller) external; + + function reweighTokens( + address[] calldata tokens, + uint96[] calldata desiredDenorms + ) external; + + function reindexTokens( + address[] calldata tokens, + uint96[] calldata desiredDenorms, + uint256[] calldata minimumBalances + ) external; + + function setMinimumBalance(address token, uint256 minimumBalance) external; + + function joinPool(uint256 poolAmountOut, uint256[] calldata maxAmountsIn) external; + + function joinswapExternAmountIn( + address tokenIn, + uint256 tokenAmountIn, + uint256 minPoolAmountOut + ) external returns (uint256/* poolAmountOut */); + + 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 gulp(address token) external; + + function swapExactAmountIn( + address tokenIn, + uint256 tokenAmountIn, + address tokenOut, + uint256 minAmountOut, + uint256 maxPrice + ) external returns (uint256/* tokenAmountOut */, uint256/* spotPriceAfter */); + + function swapExactAmountOut( + address tokenIn, + uint256 maxAmountIn, + address tokenOut, + uint256 tokenAmountOut, + uint256 maxPrice + ) external returns (uint256 /* tokenAmountIn */, uint256 /* spotPriceAfter */); + + function isPublicSwap() external view returns (bool); + + function getSwapFee() external view returns (uint256/* swapFee */); + + function getExitFee() external view returns (uint256/* exitFee */); + + function getController() external view returns (address); + + function getExitFeeRecipient() external view returns (address); + + function isBound(address t) external view returns (bool); + + function getNumTokens() external view returns (uint256); + + function getCurrentTokens() external view returns (address[] memory tokens); + + function getCurrentDesiredTokens() external view returns (address[] memory tokens); + + function getDenormalizedWeight(address token) external view returns (uint256/* denorm */); + + function getTokenRecord(address token) external view returns (Record memory record); + + function extrapolatePoolValueFromToken() external view returns (address/* token */, uint256/* extrapolatedValue */); + + function getTotalDenormalizedWeight() external view returns (uint256); + + function getBalance(address token) external view returns (uint256); + + function getMinimumBalance(address token) external view returns (uint256); + + function getUsedBalance(address token) external view returns (uint256); + + function getSpotPrice(address tokenIn, address tokenOut) external view returns (uint256); +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/IndexPool.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/IndexPool.sol new file mode 100644 index 0000000000..647c59bdb3 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/IndexPool.sol @@ -0,0 +1,1368 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.6.0; +pragma experimental ABIEncoderV2; + +/* ========== Internal Inheritance ========== */ +import "./BToken.sol"; +import "./BMath.sol"; + +/* ========== Internal Interfaces ========== */ +import "./IIndexPool.sol"; +import "./ICompLikeToken.sol"; + + +/************************************************************************************************ +Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol + +This source code has been modified from the original, which was copied from the github repository +at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. + +Subject to the GPL-3.0 license +*************************************************************************************************/ + + +contract IndexPool is BToken, BMath, IIndexPool { +/* ========== Modifiers ========== */ + + modifier _lock_ { + require(!_mutex, "ERR_REENTRY"); + _mutex = true; + _; + _mutex = false; + } + + modifier _viewlock_() { + require(!_mutex, "ERR_REENTRY"); + _; + } + + modifier _control_ { + require(msg.sender == _controller, "ERR_NOT_CONTROLLER"); + _; + } + + modifier _public_ { + require(_publicSwap, "ERR_NOT_PUBLIC"); + _; + } + +/* ========== Storage ========== */ + + bool internal _mutex; + + // Account with CONTROL role. Able to modify the swap fee, + // adjust token weights, bind and unbind tokens and lock + // public swaps & joins. + address internal _controller; + + // Contract that handles unbound tokens. + TokenUnbindHandler internal _unbindHandler; + + // True if PUBLIC can call SWAP & JOIN functions + bool internal _publicSwap; + + // `setSwapFee` requires CONTROL + uint256 internal _swapFee; + + // Array of underlying tokens in the pool. + address[] internal _tokens; + + // Internal records of the pool's underlying tokens + mapping(address => Record) internal _records; + + // Total denormalized weight of the pool. + uint256 internal _totalWeight; + + // Minimum balances for tokens which have been added without the + // requisite initial balance. + mapping(address => uint256) internal _minimumBalances; + + // Recipient for exit fees + address internal _exitFeeRecipient; + +/* ========== Controls ========== */ + + /** + * @dev Sets the controller address and the token name & symbol. + * + * Note: This saves on storage costs for multi-step pool deployment. + * + * @param controller Controller of the pool + * @param name Name of the pool token + * @param symbol Symbol of the pool token + * @param exitFeeRecipient Address that receives exit fees + */ + function configure( + address controller, + string calldata name, + string calldata symbol, + address exitFeeRecipient + ) external override { + require(_controller == address(0), "ERR_CONFIGURED"); + require(controller != address(0) && exitFeeRecipient != address(0), "ERR_NULL_ADDRESS"); + _controller = controller; + // default fee is 2% + _swapFee = BONE / 50; + _exitFeeRecipient = exitFeeRecipient; + _initializeToken(name, symbol); + } + + /** + * @dev Sets up the initial assets for the pool. + * + * Note: `tokenProvider` must have approved the pool to transfer the + * corresponding `balances` of `tokens`. + * + * @param tokens Underlying tokens to initialize the pool with + * @param balances Initial balances to transfer + * @param denorms Initial denormalized weights for the tokens + * @param tokenProvider Address to transfer the balances from + */ + function initialize( + address[] calldata tokens, + uint256[] calldata balances, + uint96[] calldata denorms, + address tokenProvider, + address unbindHandler + ) + external + override + _control_ + { + require(_tokens.length == 0, "ERR_INITIALIZED"); + uint256 len = tokens.length; + require(len >= MIN_BOUND_TOKENS, "ERR_MIN_TOKENS"); + require(len <= MAX_BOUND_TOKENS, "ERR_MAX_TOKENS"); + require(balances.length == len && denorms.length == len, "ERR_ARR_LEN"); + uint256 totalWeight = 0; + for (uint256 i = 0; i < len; i++) { + address token = tokens[i]; + uint96 denorm = denorms[i]; + uint256 balance = balances[i]; + require(denorm >= MIN_WEIGHT, "ERR_MIN_WEIGHT"); + require(denorm <= MAX_WEIGHT, "ERR_MAX_WEIGHT"); + require(balance >= MIN_BALANCE, "ERR_MIN_BALANCE"); + _records[token] = Record({ + bound: true, + ready: true, + lastDenormUpdate: uint40(now), + denorm: denorm, + desiredDenorm: denorm, + index: uint8(i), + balance: balance + }); + _tokens.push(token); + totalWeight = badd(totalWeight, denorm); + _pullUnderlying(token, tokenProvider, balance); + } + require(totalWeight <= MAX_TOTAL_WEIGHT, "ERR_MAX_TOTAL_WEIGHT"); + _totalWeight = totalWeight; + _publicSwap = true; + emit LOG_PUBLIC_SWAP_ENABLED(); + _mintPoolShare(INIT_POOL_SUPPLY); + _pushPoolShare(tokenProvider, INIT_POOL_SUPPLY); + _unbindHandler = TokenUnbindHandler(unbindHandler); + } + + /** + * @dev Set the swap fee. + * Note: Swap fee must be between 0.0001% and 10% + */ + function setSwapFee(uint256 swapFee) external override _control_ { + require(swapFee >= MIN_FEE && swapFee <= MAX_FEE, "ERR_INVALID_FEE"); + _swapFee = swapFee; + emit LOG_SWAP_FEE_UPDATED(swapFee); + } + + /** + * @dev Delegate a comp-like governance token to an address + * specified by the controller. + */ + function delegateCompLikeToken(address token,address delegatee) + external + override + _control_ + { + ICompLikeToken(token).delegate(delegatee); + } + + /** + * @dev Set the exit fee recipient address. + */ + function setExitFeeRecipient(address exitFeeRecipient) external override _control_ { + require(exitFeeRecipient != address(0), "ERR_NULL_ADDRESS"); + _exitFeeRecipient = exitFeeRecipient; + emit LOG_EXIT_FEE_RECIPIENT_UPDATED(exitFeeRecipient); + } + + /** + * @dev Set the controller address + */ + function setController(address controller) external override _control_ { + require(controller != address(0), "ERR_NULL_ADDRESS"); + _controller = controller; + emit LOG_CONTROLLER_UPDATED(controller); + } + +/* ========== Token Management Actions ========== */ + + /** + * @dev Sets the desired weights for the pool tokens, which + * will be adjusted over time as they are swapped. + * + * Note: This does not check for duplicate tokens or that the total + * of the desired weights is equal to the target total weight (25). + * Those assumptions should be met in the controller. Further, the + * provided tokens should only include the tokens which are not set + * for removal. + */ + function reweighTokens( + address[] calldata tokens, + uint96[] calldata desiredDenorms + ) + external + override + _lock_ + _control_ + { + require(desiredDenorms.length == tokens.length, "ERR_ARR_LEN"); + for (uint256 i = 0; i < tokens.length; i++) + _setDesiredDenorm(tokens[i], desiredDenorms[i]); + } + + /** + * @dev Update the underlying assets held by the pool and their associated + * weights. Tokens which are not currently bound will be gradually added + * as they are swapped in to reach the provided minimum balances, which must + * be an amount of tokens worth the minimum weight of the total pool value. + * If a currently bound token is not received in this call, the token's + * desired weight will be set to 0. + */ + function reindexTokens( + address[] calldata tokens, + uint96[] calldata desiredDenorms, + uint256[] calldata minimumBalances + ) + external + override + _lock_ + _control_ + { + require( + desiredDenorms.length == tokens.length && minimumBalances.length == tokens.length, + "ERR_ARR_LEN" + ); + // This size may not be the same as the input size, as it is possible + // to temporarily exceed the index size while tokens are being phased in + // or out. + uint256 tLen = _tokens.length; + bool[] memory receivedIndices = new bool[](tLen); + // We need to read token records in two separate loops, so + // write them to memory to avoid duplicate storage reads. + Record[] memory records = new Record[](tokens.length); + // Read all the records from storage and mark which of the existing tokens + // were represented in the reindex call. + for (uint256 i = 0; i < tokens.length; i++) { + records[i] = _records[tokens[i]]; + if (records[i].bound) receivedIndices[records[i].index] = true; + } + // If any bound tokens were not sent in this call, set their desired weights to 0. + for (uint256 i = 0; i < tLen; i++) { + if (!receivedIndices[i]) { + _setDesiredDenorm(_tokens[i], 0); + } + } + for (uint256 i = 0; i < tokens.length; i++) { + address token = tokens[i]; + // If an input weight is less than the minimum weight, use that instead. + uint96 denorm = desiredDenorms[i]; + if (denorm < MIN_WEIGHT) denorm = uint96(MIN_WEIGHT); + if (!records[i].bound) { + // If the token is not bound, bind it. + _bind(token, minimumBalances[i], denorm); + } else { + _setDesiredDenorm(token, denorm); + } + } + } + + /** + * @dev Updates the minimum balance for an uninitialized token. + * This becomes useful if a token's external price significantly + * rises after being bound, since the pool can not send a token + * out until it reaches the minimum balance. + */ + function setMinimumBalance( + address token, + uint256 minimumBalance + ) + external + override + _control_ + { + Record storage record = _records[token]; + require(record.bound, "ERR_NOT_BOUND"); + require(!record.ready, "ERR_READY"); + _minimumBalances[token] = minimumBalance; + emit LOG_MINIMUM_BALANCE_UPDATED(token, minimumBalance); + } + +/* ========== Liquidity Provider Actions ========== */ + + /** + * @dev Mint new pool tokens by providing the proportional amount of each + * underlying token's balance relative to the proportion of pool tokens minted. + * + * For any underlying tokens which are not initialized, the caller must provide + * the proportional share of the minimum balance for the token rather than the + * actual balance. + * + * @param poolAmountOut Amount of pool tokens to mint + * @param maxAmountsIn Maximum amount of each token to pay in the same + * order as the pool's _tokens list. + */ + function joinPool(uint256 poolAmountOut, uint256[] calldata maxAmountsIn) + external + override + _lock_ + _public_ + { + uint256 poolTotal = totalSupply(); + uint256 ratio = bdiv(poolAmountOut, poolTotal); + require(ratio != 0, "ERR_MATH_APPROX"); + require(maxAmountsIn.length == _tokens.length, "ERR_ARR_LEN"); + + for (uint256 i = 0; i < maxAmountsIn.length; i++) { + address t = _tokens[i]; + (Record memory record, uint256 realBalance) = _getInputToken(t); + uint256 tokenAmountIn = bmul(ratio, record.balance); + require(tokenAmountIn != 0, "ERR_MATH_APPROX"); + require(tokenAmountIn <= maxAmountsIn[i], "ERR_LIMIT_IN"); + _updateInputToken(t, record, badd(realBalance, tokenAmountIn)); + emit LOG_JOIN(msg.sender, t, tokenAmountIn); + _pullUnderlying(t, msg.sender, tokenAmountIn); + } + _mintPoolShare(poolAmountOut); + _pushPoolShare(msg.sender, poolAmountOut); + } + + /** + * @dev Pay `tokenAmountIn` of `tokenIn` to mint at least `minPoolAmountOut` + * pool tokens. + * + * The pool implicitly swaps `(1- weightTokenIn) * tokenAmountIn` to the other + * underlying tokens. Thus a swap fee is charged against the input tokens. + * + * @param tokenIn Token to send the pool + * @param tokenAmountIn Exact amount of `tokenIn` to pay + * @param minPoolAmountOut Minimum amount of pool tokens to mint + * @return poolAmountOut - Amount of pool tokens minted + */ + function joinswapExternAmountIn( + address tokenIn, + uint256 tokenAmountIn, + uint256 minPoolAmountOut + ) + external + override + _lock_ + _public_ + returns (uint256/* poolAmountOut */) + { + (Record memory inRecord, uint256 realInBalance) = _getInputToken(tokenIn); + + require(tokenAmountIn != 0, "ERR_ZERO_IN"); + + require( + tokenAmountIn <= bmul(inRecord.balance, MAX_IN_RATIO), + "ERR_MAX_IN_RATIO" + ); + + uint256 poolAmountOut = calcPoolOutGivenSingleIn( + inRecord.balance, + inRecord.denorm, + _totalSupply, + _totalWeight, + tokenAmountIn, + _swapFee + ); + + require(poolAmountOut >= minPoolAmountOut, "ERR_LIMIT_OUT"); + + _updateInputToken(tokenIn, inRecord, badd(realInBalance, tokenAmountIn)); + + emit LOG_JOIN(msg.sender, tokenIn, tokenAmountIn); + + _mintPoolShare(poolAmountOut); + _pushPoolShare(msg.sender, poolAmountOut); + _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); + + return poolAmountOut; + } + + /** + * @dev Pay up to `maxAmountIn` of `tokenIn` to mint exactly `poolAmountOut`. + * + * The pool implicitly swaps `(1- weightTokenIn) * tokenAmountIn` to the other + * underlying tokens. Thus a swap fee is charged against the input tokens. + * + * @param tokenIn Token to send the pool + * @param poolAmountOut Exact amount of pool tokens to mint + * @param maxAmountIn Maximum amount of `tokenIn` to pay + * @return tokenAmountIn - Amount of `tokenIn` paid + */ + function joinswapPoolAmountOut( + address tokenIn, + uint256 poolAmountOut, + uint256 maxAmountIn + ) + external + override + _lock_ + _public_ + returns (uint256/* tokenAmountIn */) + { + (Record memory inRecord, uint256 realInBalance) = _getInputToken(tokenIn); + + uint256 tokenAmountIn = calcSingleInGivenPoolOut( + inRecord.balance, + inRecord.denorm, + _totalSupply, + _totalWeight, + poolAmountOut, + _swapFee + ); + + require(tokenAmountIn != 0, "ERR_MATH_APPROX"); + require(tokenAmountIn <= maxAmountIn, "ERR_LIMIT_IN"); + + require( + tokenAmountIn <= bmul(inRecord.balance, MAX_IN_RATIO), + "ERR_MAX_IN_RATIO" + ); + + _updateInputToken(tokenIn, inRecord, badd(realInBalance, tokenAmountIn)); + + emit LOG_JOIN(msg.sender, tokenIn, tokenAmountIn); + + _mintPoolShare(poolAmountOut); + _pushPoolShare(msg.sender, poolAmountOut); + _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); + + return tokenAmountIn; + } + + /** + * @dev Burns `poolAmountIn` pool tokens in exchange for the amounts of each + * underlying token's balance proportional to the ratio of tokens burned to + * total pool supply. The amount of each token transferred to the caller must + * be greater than or equal to the associated minimum output amount from the + * `minAmountsOut` array. + * + * @param poolAmountIn Exact amount of pool tokens to burn + * @param minAmountsOut Minimum amount of each token to receive, in the same + * order as the pool's _tokens list. + */ + function exitPool(uint256 poolAmountIn, uint256[] calldata minAmountsOut) + external + override + _lock_ + { + require(minAmountsOut.length == _tokens.length, "ERR_ARR_LEN"); + uint256 poolTotal = totalSupply(); + uint256 exitFee = bmul(poolAmountIn, EXIT_FEE); + uint256 pAiAfterExitFee = bsub(poolAmountIn, exitFee); + uint256 ratio = bdiv(pAiAfterExitFee, poolTotal); + require(ratio != 0, "ERR_MATH_APPROX"); + + _pullPoolShare(msg.sender, poolAmountIn); + _pushPoolShare(_exitFeeRecipient, exitFee); + _burnPoolShare(pAiAfterExitFee); + for (uint256 i = 0; i < minAmountsOut.length; i++) { + address t = _tokens[i]; + Record memory record = _records[t]; + if (record.ready) { + uint256 tokenAmountOut = bmul(ratio, record.balance); + require(tokenAmountOut != 0, "ERR_MATH_APPROX"); + require(tokenAmountOut >= minAmountsOut[i], "ERR_LIMIT_OUT"); + + _records[t].balance = bsub(record.balance, tokenAmountOut); + emit LOG_EXIT(msg.sender, t, tokenAmountOut); + _pushUnderlying(t, msg.sender, tokenAmountOut); + } else { + // If the token is not initialized, it can not exit the pool. + require(minAmountsOut[i] == 0, "ERR_OUT_NOT_READY"); + } + } + } + + /** + * @dev Burns `poolAmountIn` pool tokens in exchange for at least `minAmountOut` + * of `tokenOut`. Returns the number of tokens sent to the caller. + * + * The pool implicitly burns the tokens for all underlying tokens and swaps them + * to the desired output token. A swap fee is charged against the output tokens. + * + * @param tokenOut Token to receive + * @param poolAmountIn Exact amount of pool tokens to burn + * @param minAmountOut Minimum amount of `tokenOut` to receive + * @return tokenAmountOut - Amount of `tokenOut` received + */ + function exitswapPoolAmountIn( + address tokenOut, + uint256 poolAmountIn, + uint256 minAmountOut + ) + external + override + _lock_ + returns (uint256/* tokenAmountOut */) + { + Record memory outRecord = _getOutputToken(tokenOut); + + uint256 tokenAmountOut = calcSingleOutGivenPoolIn( + outRecord.balance, + outRecord.denorm, + _totalSupply, + _totalWeight, + poolAmountIn, + _swapFee + ); + + require(tokenAmountOut >= minAmountOut, "ERR_LIMIT_OUT"); + + require( + tokenAmountOut <= bmul(outRecord.balance, MAX_OUT_RATIO), + "ERR_MAX_OUT_RATIO" + ); + + _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); + _records[tokenOut].balance = bsub(outRecord.balance, tokenAmountOut); + _decreaseDenorm(outRecord, tokenOut); + uint256 exitFee = bmul(poolAmountIn, EXIT_FEE); + + emit LOG_EXIT(msg.sender, tokenOut, tokenAmountOut); + + _pullPoolShare(msg.sender, poolAmountIn); + _burnPoolShare(bsub(poolAmountIn, exitFee)); + _pushPoolShare(_exitFeeRecipient, exitFee); + + return tokenAmountOut; + } + + /** + * @dev Burn up to `maxPoolAmountIn` for exactly `tokenAmountOut` of `tokenOut`. + * Returns the number of pool tokens burned. + * + * The pool implicitly burns the tokens for all underlying tokens and swaps them + * to the desired output token. A swap fee is charged against the output tokens. + * + * @param tokenOut Token to receive + * @param tokenAmountOut Exact amount of `tokenOut` to receive + * @param maxPoolAmountIn Maximum amount of pool tokens to burn + * @return poolAmountIn - Amount of pool tokens burned + */ + function exitswapExternAmountOut( + address tokenOut, + uint256 tokenAmountOut, + uint256 maxPoolAmountIn + ) + external + override + _lock_ + returns (uint256/* poolAmountIn */) + { + Record memory outRecord = _getOutputToken(tokenOut); + require( + tokenAmountOut <= bmul(outRecord.balance, MAX_OUT_RATIO), + "ERR_MAX_OUT_RATIO" + ); + + uint256 poolAmountIn = calcPoolInGivenSingleOut( + outRecord.balance, + outRecord.denorm, + _totalSupply, + _totalWeight, + tokenAmountOut, + _swapFee + ); + + require(poolAmountIn != 0, "ERR_MATH_APPROX"); + require(poolAmountIn <= maxPoolAmountIn, "ERR_LIMIT_IN"); + + _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); + _records[tokenOut].balance = bsub(outRecord.balance, tokenAmountOut); + _decreaseDenorm(outRecord, tokenOut); + + uint256 exitFee = bmul(poolAmountIn, EXIT_FEE); + + emit LOG_EXIT(msg.sender, tokenOut, tokenAmountOut); + + _pullPoolShare(msg.sender, poolAmountIn); + _burnPoolShare(bsub(poolAmountIn, exitFee)); + _pushPoolShare(_exitFeeRecipient, exitFee); + + return poolAmountIn; + } + +/* ========== Other ========== */ + + /** + * @dev Absorb any tokens that have been sent to the pool. + * If the token is not bound, it will be sent to the unbound + * token handler. + */ + function gulp(address token) external override _lock_ { + Record storage record = _records[token]; + uint256 balance = IERC20(token).balanceOf(address(this)); + if (record.bound) { + if (!record.ready) { + uint256 minimumBalance = _minimumBalances[token]; + if (balance >= minimumBalance) { + _minimumBalances[token] = 0; + record.ready = true; + emit LOG_TOKEN_READY(token); + uint256 additionalBalance = bsub(balance, minimumBalance); + uint256 balRatio = bdiv(additionalBalance, minimumBalance); + uint96 newDenorm = uint96(badd(MIN_WEIGHT, bmul(MIN_WEIGHT, balRatio))); + record.denorm = newDenorm; + record.lastDenormUpdate = uint40(now); + _totalWeight = badd(_totalWeight, newDenorm); + emit LOG_DENORM_UPDATED(token, record.denorm); + } + } + _records[token].balance = balance; + } else { + _pushUnderlying(token, address(_unbindHandler), balance); + _unbindHandler.handleUnbindToken(token, balance); + } + } + +/* ========== Token Swaps ========== */ + + /** + * @dev Execute a token swap with a specified amount of input + * tokens and a minimum amount of output tokens. + * + * Note: Will revert if `tokenOut` is uninitialized. + * + * @param tokenIn Token to swap in + * @param tokenAmountIn Exact amount of `tokenIn` to swap in + * @param tokenOut Token to swap out + * @param minAmountOut Minimum amount of `tokenOut` to receive + * @param maxPrice Maximum ratio of input to output tokens + * @return (tokenAmountOut, spotPriceAfter) + */ + function swapExactAmountIn( + address tokenIn, + uint256 tokenAmountIn, + address tokenOut, + uint256 minAmountOut, + uint256 maxPrice + ) + external + override + _lock_ + _public_ + returns (uint256/* tokenAmountOut */, uint256/* spotPriceAfter */) + { + (Record memory inRecord, uint256 realInBalance) = _getInputToken(tokenIn); + Record memory outRecord = _getOutputToken(tokenOut); + + require( + tokenAmountIn <= bmul(inRecord.balance, MAX_IN_RATIO), + "ERR_MAX_IN_RATIO" + ); + + uint256 spotPriceBefore = calcSpotPrice( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + _swapFee + ); + require(spotPriceBefore <= maxPrice, "ERR_BAD_LIMIT_PRICE"); + + uint256 tokenAmountOut = calcOutGivenIn( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + tokenAmountIn, + _swapFee + ); + + require(tokenAmountOut >= minAmountOut, "ERR_LIMIT_OUT"); + + _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); + _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); + + // Update the in-memory record for the spotPriceAfter calculation, + // then update the storage record with the local balance. + outRecord.balance = bsub(outRecord.balance, tokenAmountOut); + _records[tokenOut].balance = outRecord.balance; + // If needed, update the output token's weight. + _decreaseDenorm(outRecord, tokenOut); + + realInBalance = badd(realInBalance, tokenAmountIn); + _updateInputToken(tokenIn, inRecord, realInBalance); + if (inRecord.ready) { + inRecord.balance = realInBalance; + } + + uint256 spotPriceAfter = calcSpotPrice( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + _swapFee + ); + + require(spotPriceAfter >= spotPriceBefore, "ERR_MATH_APPROX_2"); + require(spotPriceAfter <= maxPrice, "ERR_LIMIT_PRICE"); + require( + spotPriceBefore <= bdiv(tokenAmountIn, tokenAmountOut), + "ERR_MATH_APPROX" + ); + + emit LOG_SWAP(msg.sender, tokenIn, tokenOut, tokenAmountIn, tokenAmountOut); + + return (tokenAmountOut, spotPriceAfter); + } + + /** + * @dev Trades at most `maxAmountIn` of `tokenIn` for exactly `tokenAmountOut` + * of `tokenOut`. + * + * Returns the actual input amount and the new spot price after the swap, + * which can not exceed `maxPrice`. + * + * @param tokenIn Token to swap in + * @param maxAmountIn Maximum amount of `tokenIn` to pay + * @param tokenOut Token to swap out + * @param tokenAmountOut Exact amount of `tokenOut` to receive + * @param maxPrice Maximum ratio of input to output tokens + * @return (tokenAmountIn, spotPriceAfter) + */ + function swapExactAmountOut( + address tokenIn, + uint256 maxAmountIn, + address tokenOut, + uint256 tokenAmountOut, + uint256 maxPrice + ) + external + override + _lock_ + _public_ + returns (uint256 /* tokenAmountIn */, uint256 /* spotPriceAfter */) + { + (Record memory inRecord, uint256 realInBalance) = _getInputToken(tokenIn); + Record memory outRecord = _getOutputToken(tokenOut); + + require( + tokenAmountOut <= bmul(outRecord.balance, MAX_OUT_RATIO), + "ERR_MAX_OUT_RATIO" + ); + + uint256 spotPriceBefore = calcSpotPrice( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + _swapFee + ); + require(spotPriceBefore <= maxPrice, "ERR_BAD_LIMIT_PRICE"); + + uint256 tokenAmountIn = calcInGivenOut( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + tokenAmountOut, + _swapFee + ); + + require(tokenAmountIn <= maxAmountIn, "ERR_LIMIT_IN"); + + _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); + _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); + + // Update the in-memory record for the spotPriceAfter calculation, + // then update the storage record with the local balance. + outRecord.balance = bsub(outRecord.balance, tokenAmountOut); + _records[tokenOut].balance = outRecord.balance; + // If needed, update the output token's weight. + _decreaseDenorm(outRecord, tokenOut); + + // Update the balance and (if necessary) weight of the input token. + realInBalance = badd(realInBalance, tokenAmountIn); + _updateInputToken(tokenIn, inRecord, realInBalance); + if (inRecord.ready) { + inRecord.balance = realInBalance; + } + + uint256 spotPriceAfter = calcSpotPrice( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + _swapFee + ); + + require(spotPriceAfter >= spotPriceBefore, "ERR_MATH_APPROX"); + require(spotPriceAfter <= maxPrice, "ERR_LIMIT_PRICE"); + require( + spotPriceBefore <= bdiv(tokenAmountIn, tokenAmountOut), + "ERR_MATH_APPROX" + ); + + emit LOG_SWAP(msg.sender, tokenIn, tokenOut, tokenAmountIn, tokenAmountOut); + + return (tokenAmountIn, spotPriceAfter); + } + +/* ========== Config Queries ========== */ + /** + * @dev Check if swapping tokens and joining the pool is allowed. + */ + function isPublicSwap() external view override returns (bool) { + return _publicSwap; + } + + function getSwapFee() external view override _viewlock_ returns (uint256/* swapFee */) { + return _swapFee; + } + + function getExitFee() external view override _viewlock_ returns (uint256/* exitFee */) { + return EXIT_FEE; + } + + /** + * @dev Returns the controller address. + */ + function getController() external view override returns (address) + { + return _controller; + } + + /** + * @dev Returns the exit fee recipient address. + */ + function getExitFeeRecipient() external view override returns (address) { + return _exitFeeRecipient; + } + +/* ========== Token Queries ========== */ + + /** + * @dev Check if a token is bound to the pool. + */ + function isBound(address t) external view override returns (bool) { + return _records[t].bound; + } + + /** + * @dev Get the number of tokens bound to the pool. + */ + function getNumTokens() external view override returns (uint256) { + return _tokens.length; + } + + /** + * @dev Get all bound tokens. + */ + function getCurrentTokens() + external + view + override + _viewlock_ + returns (address[] memory tokens) + { + tokens = _tokens; + } + + /** + * @dev Returns the list of tokens which have a desired weight above 0. + * Tokens with a desired weight of 0 are set to be phased out of the pool. + */ + function getCurrentDesiredTokens() + external + view + override + _viewlock_ + returns (address[] memory tokens) + { + address[] memory tempTokens = _tokens; + tokens = new address[](tempTokens.length); + uint256 usedIndex = 0; + for (uint256 i = 0; i < tokens.length; i++) { + address token = tempTokens[i]; + if (_records[token].desiredDenorm > 0) { + tokens[usedIndex++] = token; + } + } + assembly { mstore(tokens, usedIndex) } + } + + /** + * @dev Returns the denormalized weight of a bound token. + */ + function getDenormalizedWeight(address token) + external + view + override + _viewlock_ + returns (uint256/* denorm */) + { + require(_records[token].bound, "ERR_NOT_BOUND"); + return _records[token].denorm; + } + + /** + * @dev Returns the record for a token bound to the pool. + */ + function getTokenRecord(address token) + external + view + override + _viewlock_ + returns (Record memory record) + { + record = _records[token]; + require(record.bound, "ERR_NOT_BOUND"); + } + + /** + * @dev Finds the first token which is both initialized and has a + * desired weight above 0, then returns the address of that token + * and the extrapolated value of the pool in terms of that token. + * + * The value is extrapolated by multiplying the token's + * balance by the reciprocal of its normalized weight. + * @return (token, extrapolatedValue) + */ + function extrapolatePoolValueFromToken() + external + view + override + _viewlock_ + returns (address/* token */, uint256/* extrapolatedValue */) + { + address token; + uint256 extrapolatedValue; + uint256 len = _tokens.length; + for (uint256 i = 0; i < len; i++) { + token = _tokens[i]; + Record storage record = _records[token]; + if (record.ready && record.desiredDenorm > 0) { + extrapolatedValue = bmul( + record.balance, + bdiv(_totalWeight, record.denorm) + ); + break; + } + } + require(extrapolatedValue > 0, "ERR_NONE_READY"); + return (token, extrapolatedValue); + } + + /** + * @dev Get the total denormalized weight of the pool. + */ + function getTotalDenormalizedWeight() + external + view + override + _viewlock_ + returns (uint256) + { + return _totalWeight; + } + + /** + * @dev Returns the stored balance of a bound token. + */ + function getBalance(address token) external view override _viewlock_ returns (uint256) { + Record storage record = _records[token]; + require(record.bound, "ERR_NOT_BOUND"); + return record.balance; + } + + /** + * @dev Get the minimum balance of an uninitialized token. + * Note: Throws if the token is initialized. + */ + function getMinimumBalance(address token) external view override _viewlock_ returns (uint256) { + Record memory record = _records[token]; + require(record.bound, "ERR_NOT_BOUND"); + require(!record.ready, "ERR_READY"); + return _minimumBalances[token]; + } + + /** + * @dev Returns the balance of a token which is used in price + * calculations. If the token is initialized, this is the + * stored balance; if not, this is the minimum balance. + */ + function getUsedBalance(address token) external view override _viewlock_ returns (uint256) { + Record memory record = _records[token]; + require(record.bound, "ERR_NOT_BOUND"); + if (!record.ready) { + return _minimumBalances[token]; + } + return record.balance; + } + +/* ========== Price Queries ========== */ + /** + * @dev Returns the spot price for `tokenOut` in terms of `tokenIn`. + */ + function getSpotPrice(address tokenIn, address tokenOut) + external + view + override + _viewlock_ + returns (uint256) + { + (Record memory inRecord,) = _getInputToken(tokenIn); + Record memory outRecord = _getOutputToken(tokenOut); + return + calcSpotPrice( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + _swapFee + ); + } + +/* ========== Pool Share Internal Functions ========== */ + + function _pullPoolShare(address from, uint256 amount) internal { + _pull(from, amount); + } + + function _pushPoolShare(address to, uint256 amount) internal { + _push(to, amount); + } + + function _mintPoolShare(uint256 amount) internal { + _mint(amount); + } + + function _burnPoolShare(uint256 amount) internal { + _burn(amount); + } + +/* ========== Underlying Token Internal Functions ========== */ + // 'Underlying' token-manipulation functions make external calls but are NOT locked + // You must `_lock_` or otherwise ensure reentry-safety + + function _pullUnderlying( + address erc20, + address from, + uint256 amount + ) internal { + (bool success, bytes memory data) = erc20.call( + abi.encodeWithSelector( + IERC20.transferFrom.selector, + from, + address(this), + amount + ) + ); + require( + success && (data.length == 0 || abi.decode(data, (bool))), + "ERR_ERC20_FALSE" + ); + } + + function _pushUnderlying( + address erc20, + address to, + uint256 amount + ) internal { + (bool success, bytes memory data) = erc20.call( + abi.encodeWithSelector( + IERC20.transfer.selector, + to, + amount + ) + ); + require( + success && (data.length == 0 || abi.decode(data, (bool))), + "ERR_ERC20_FALSE" + ); + } + +/* ========== Token Management Internal Functions ========== */ + + /** + * @dev Bind a token by address without actually depositing a balance. + * The token will be unable to be swapped out until it reaches the minimum balance. + * Note: Token must not already be bound. + * Note: `minimumBalance` should represent an amount of the token which is worth + * the portion of the current pool value represented by the minimum weight. + * @param token Address of the token to bind + * @param minimumBalance minimum balance to reach before the token can be swapped out + * @param desiredDenorm Desired weight for the token. + */ + function _bind( + address token, + uint256 minimumBalance, + uint96 desiredDenorm + ) internal { + require(!_records[token].bound, "ERR_IS_BOUND"); + + require(desiredDenorm >= MIN_WEIGHT, "ERR_MIN_WEIGHT"); + require(desiredDenorm <= MAX_WEIGHT, "ERR_MAX_WEIGHT"); + require(minimumBalance >= MIN_BALANCE, "ERR_MIN_BALANCE"); + + _records[token] = Record({ + bound: true, + ready: false, + lastDenormUpdate: 0, + denorm: 0, + desiredDenorm: desiredDenorm, + index: uint8(_tokens.length), + balance: 0 + }); + _tokens.push(token); + _minimumBalances[token] = minimumBalance; + emit LOG_TOKEN_ADDED(token, desiredDenorm, minimumBalance); + } + + /** + * @dev Remove a token from the pool. + * Replaces the address in the tokens array with the last address, + * then removes it from the array. + * Note: This should only be called after the total weight has been adjusted. + * Note: Must be called in a function with: + * - _lock_ modifier to prevent reentrance + * - requirement that the token is bound + */ + function _unbind(address token) internal { + Record memory record = _records[token]; + uint256 tokenBalance = record.balance; + + // Swap the token-to-unbind with the last token, + // then delete the last token + uint256 index = record.index; + uint256 last = _tokens.length - 1; + // Only swap the token with the last token if it is not + // already at the end of the array. + if (index != last) { + _tokens[index] = _tokens[last]; + _records[_tokens[index]].index = uint8(index); + } + _tokens.pop(); + _records[token] = Record({ + bound: false, + ready: false, + lastDenormUpdate: 0, + denorm: 0, + desiredDenorm: 0, + index: 0, + balance: 0 + }); + // transfer any remaining tokens out + _pushUnderlying(token, address(_unbindHandler), tokenBalance); + _unbindHandler.handleUnbindToken(token, tokenBalance); + emit LOG_TOKEN_REMOVED(token); + } + + function _setDesiredDenorm(address token, uint96 desiredDenorm) internal { + Record storage record = _records[token]; + require(record.bound, "ERR_NOT_BOUND"); + // If the desired weight is 0, this will trigger a gradual unbinding of the token. + // Therefore the weight only needs to be above the minimum weight if it isn't 0. + require( + desiredDenorm >= MIN_WEIGHT || desiredDenorm == 0, + "ERR_MIN_WEIGHT" + ); + require(desiredDenorm <= MAX_WEIGHT, "ERR_MAX_WEIGHT"); + record.desiredDenorm = desiredDenorm; + emit LOG_DESIRED_DENORM_SET(token, desiredDenorm); + } + + function _increaseDenorm(Record memory record, address token) internal { + // If the weight does not need to increase or the token is not + // initialized, don't do anything. + if ( + record.denorm >= record.desiredDenorm || + !record.ready || + now - record.lastDenormUpdate < WEIGHT_UPDATE_DELAY + ) return; + uint96 oldWeight = record.denorm; + uint96 denorm = record.desiredDenorm; + uint256 maxDiff = bmul(oldWeight, WEIGHT_CHANGE_PCT); + uint256 diff = bsub(denorm, oldWeight); + if (diff > maxDiff) { + denorm = uint96(badd(oldWeight, maxDiff)); + diff = maxDiff; + } + // If new total weight exceeds the maximum, do not update + uint256 newTotalWeight = badd(_totalWeight, diff); + if (newTotalWeight > MAX_TOTAL_WEIGHT) return; + _totalWeight = newTotalWeight; + // Update the in-memory denorm value for spot-price computations. + record.denorm = denorm; + // Update the storage record + _records[token].denorm = denorm; + _records[token].lastDenormUpdate = uint40(now); + emit LOG_DENORM_UPDATED(token, denorm); + } + + function _decreaseDenorm(Record memory record, address token) internal { + // If the weight does not need to decrease, don't do anything. + if ( + record.denorm <= record.desiredDenorm || + !record.ready || + now - record.lastDenormUpdate < WEIGHT_UPDATE_DELAY + ) return; + uint96 oldWeight = record.denorm; + uint96 denorm = record.desiredDenorm; + uint256 maxDiff = bmul(oldWeight, WEIGHT_CHANGE_PCT); + uint256 diff = bsub(oldWeight, denorm); + if (diff > maxDiff) { + denorm = uint96(bsub(oldWeight, maxDiff)); + diff = maxDiff; + } + if (denorm <= MIN_WEIGHT) { + denorm = 0; + _totalWeight = bsub(_totalWeight, denorm); + // Because this is removing the token from the pool, the + // in-memory denorm value is irrelevant, as it is only used + // to calculate the new spot price, but the spot price calc + // will throw if it is passed 0 for the denorm. + _unbind(token); + } else { + _totalWeight = bsub(_totalWeight, diff); + // Update the in-memory denorm value for spot-price computations. + record.denorm = denorm; + // Update the stored denorm value + _records[token].denorm = denorm; + _records[token].lastDenormUpdate = uint40(now); + emit LOG_DENORM_UPDATED(token, denorm); + } + } + + /** + * @dev Handles weight changes and initialization of an + * input token. + * + * If the token is not initialized and the new balance is + * still below the minimum, this will not do anything. + * + * If the token is not initialized but the new balance will + * bring the token above the minimum balance, this will + * mark the token as initialized, remove the minimum + * balance and set the weight to the minimum weight plus + * 1%. + * + * + * @param token Address of the input token + * @param record Token record with minimums applied to the balance + * and weight if the token was uninitialized. + */ + function _updateInputToken( + address token, + Record memory record, + uint256 realBalance + ) + internal + { + if (!record.ready) { + // Check if the minimum balance has been reached + if (realBalance >= record.balance) { + // Remove the minimum balance record + _minimumBalances[token] = 0; + // Mark the token as initialized + _records[token].ready = true; + record.ready = true; + emit LOG_TOKEN_READY(token); + // Set the initial denorm value to the minimum weight times one plus + // the ratio of the increase in balance over the minimum to the minimum + // balance. + // weight = (1 + ((bal - min_bal) / min_bal)) * min_weight + uint256 additionalBalance = bsub(realBalance, record.balance); + uint256 balRatio = bdiv(additionalBalance, record.balance); + record.denorm = uint96(badd(MIN_WEIGHT, bmul(MIN_WEIGHT, balRatio))); + _records[token].denorm = record.denorm; + _records[token].lastDenormUpdate = uint40(now); + _totalWeight = badd(_totalWeight, record.denorm); + emit LOG_DENORM_UPDATED(token, record.denorm); + } else { + uint256 realToMinRatio = bdiv( + bsub(record.balance, realBalance), + record.balance + ); + uint256 weightPremium = bmul(MIN_WEIGHT / 10, realToMinRatio); + record.denorm = uint96(badd(MIN_WEIGHT, weightPremium)); + } + // If the token is still not ready, do not adjust the weight. + } else { + // If the token is already initialized, update the weight (if any adjustment + // is needed). + _increaseDenorm(record, token); + } + // Regardless of whether the token is initialized, store the actual new balance. + _records[token].balance = realBalance; + } + +/* ========== Token Query Internal Functions ========== */ + + /** + * @dev Get the record for a token which is being swapped in. + * The token must be bound to the pool. If the token is not + * initialized (meaning it does not have the minimum balance) + * this function will return the actual balance of the token + * which the pool holds, but set the record's balance and weight + * to the token's minimum balance and the pool's minimum weight. + * This allows the token swap to be priced correctly even if the + * pool does not own any of the tokens. + */ + function _getInputToken(address token) + internal + view + returns (Record memory record, uint256 realBalance) + { + record = _records[token]; + require(record.bound, "ERR_NOT_BOUND"); + + realBalance = record.balance; + // If the input token is not initialized, we use the minimum + // initial weight and minimum initial balance instead of the + // real values for price and output calculations. + if (!record.ready) { + record.balance = _minimumBalances[token]; + uint256 realToMinRatio = bdiv( + bsub(record.balance, realBalance), + record.balance + ); + uint256 weightPremium = bmul(MIN_WEIGHT / 10, realToMinRatio); + record.denorm = uint96(badd(MIN_WEIGHT, weightPremium)); + } + } + + function _getOutputToken(address token) + internal + view + returns (Record memory record) + { + record = _records[token]; + require(record.bound, "ERR_NOT_BOUND"); + // Tokens which have not reached their minimum balance can not be + // swapped out. + require(record.ready, "ERR_OUT_NOT_READY"); + } +} + + +interface TokenUnbindHandler { + /** + * @dev Receive `amount` of `token` from the pool. + */ + function handleUnbindToken(address token, uint256 amount) external; +} \ 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 0000000000..3b091f62fb --- /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/SafeMath.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/SafeMath.sol new file mode 100644 index 0000000000..b644454dd5 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/SafeMath.sol @@ -0,0 +1,159 @@ +// 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; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/Safemoon.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/Safemoon.sol new file mode 100644 index 0000000000..74cdc8fddd --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/Safemoon.sol @@ -0,0 +1,1166 @@ +/** + *Submitted for verification at BscScan.com on 2021-03-01 +*/ + +/** + *Submitted for verification at BscScan.com on 2021-03-01 +*/ + +/** + + #BEE + + #LIQ+#RFI+#SHIB+#DOGE = #BEE + + #SAFEMOON features: + 3% fee auto add to the liquidity pool to locked forever when selling + 2% fee auto distribute to all holders + I created a black hole so #Bee token will deflate itself in supply with every transaction + 50% Supply is burned at start. + + + */ + +pragma solidity ^0.6.11; +// SPDX-License-Identifier: Unlicensed +interface IERC20 { + + 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; + } +} + +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; + } +} + + +/** + * @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) { + // 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); + } + + /** + * @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); + } + } + } +} + +/** + * @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; + address private _previousOwner; + uint256 private _lockTime; + + 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 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; + } + + function geUnlockTime() public view returns (uint256) { + return _lockTime; + } + + //Locks the contract for owner for the amount of time provided + function lock(uint256 time) public virtual onlyOwner { + _previousOwner = _owner; + _owner = address(0); + _lockTime = now + time; + emit OwnershipTransferred(_owner, address(0)); + } + + //Unlocks the contract for owner when _lockTime is exceeds + function unlock() public virtual { + require(_previousOwner == msg.sender, "You don't have permission to unlock"); + require(now > _lockTime , "Contract is locked until 7 days"); + emit OwnershipTransferred(_owner, _previousOwner); + _owner = _previousOwner; + } +} + +// pragma solidity >=0.5.0; + +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; +} + + +// pragma solidity >=0.5.0; + +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; +} + +// 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 SafeMoon is Context, IERC20, Ownable { + using SafeMath for uint256; + using Address for address; + + 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; + + uint256 private constant MAX = ~uint256(0); + uint256 private _tTotal = 1000000000 * 10**6 * 10**9; + uint256 private _rTotal = (MAX - (MAX % _tTotal)); + uint256 private _tFeeTotal; + + string private _name = "SafeMoon"; + string private _symbol = "SAFEMOON"; + uint8 private _decimals = 9; + + uint256 public _taxFee = 5; + uint256 private _previousTaxFee = _taxFee; + + uint256 public _liquidityFee = 5; + uint256 private _previousLiquidityFee = _liquidityFee; + + IUniswapV2Router02 public immutable uniswapV2Router; + address public immutable uniswapV2Pair; + + bool inSwapAndLiquify; + bool public swapAndLiquifyEnabled = true; + + uint256 public _maxTxAmount = 5000000 * 10**6 * 10**9; + uint256 private numTokensSellToAddToLiquidity = 500000 * 10**6 * 10**9; + + event MinTokensBeforeSwapUpdated(uint256 minTokensBeforeSwap); + 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(0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F); + // Create a uniswap pair for this new token + uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()) + .createPair(address(this), _uniswapV2Router.WETH()); + + // set the rest of the contract variables + uniswapV2Router = _uniswapV2Router; + + //exclude owner and this contract from fee + _isExcludedFromFee[owner()] = true; + _isExcludedFromFee[address(this)] = true; + + emit Transfer(address(0), _msgSender(), _tTotal); + } + + 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; + } + + function totalSupply() public 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) public override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + function allowance(address owner, address spender) public view override returns (uint256) { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) public override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + function transferFrom(address sender, address recipient, uint256 amount) public 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 isExcludedFromReward(address account) public view returns (bool) { + return _isExcluded[account]; + } + + function totalFees() public view returns (uint256) { + return _tFeeTotal; + } + + function deliver(uint256 tAmount) public { + 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) public 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) public 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 _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 excludeFromFee(address account) public onlyOwner { + _isExcludedFromFee[account] = true; + } + + function includeInFee(address account) public onlyOwner { + _isExcludedFromFee[account] = false; + } + + function setTaxFeePercent(uint256 taxFee) external onlyOwner() { + _taxFee = taxFee; + } + + function setLiquidityFeePercent(uint256 liquidityFee) external onlyOwner() { + _liquidityFee = liquidityFee; + } + + function setMaxTxPercent(uint256 maxTxPercent) external onlyOwner() { + _maxTxAmount = _tTotal.mul(maxTxPercent).div( + 10**2 + ); + } + + function setSwapAndLiquifyEnabled(bool _enabled) public onlyOwner { + swapAndLiquifyEnabled = _enabled; + emit SwapAndLiquifyEnabledUpdated(_enabled); + } + + //to recieve ETH from uniswapV2Router when swaping + receive() external payable {} + + 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) public view returns(bool) { + return _isExcludedFromFee[account]; + } + + 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."); + + // is the token balance of this contract address over the min number of + // tokens that we need to initiate a swap + liquidity lock? + // also, don't get caught in a circular liquidity event. + // also, don't swap & liquify if sender is uniswap pair. + uint256 contractTokenBalance = balanceOf(address(this)); + + if(contractTokenBalance >= _maxTxAmount) + { + contractTokenBalance = _maxTxAmount; + } + + bool overMinTokenBalance = contractTokenBalance >= numTokensSellToAddToLiquidity; + if ( + overMinTokenBalance && + !inSwapAndLiquify && + from != uniswapV2Pair && + swapAndLiquifyEnabled + ) { + contractTokenBalance = numTokensSellToAddToLiquidity; + //add liquidity + swapAndLiquify(contractTokenBalance); + } + + //indicates if fee should be deducted from transfer + bool takeFee = true; + + //if any account belongs to _isExcludedFromFee account then remove the fee + if(_isExcludedFromFee[from] || _isExcludedFromFee[to]){ + takeFee = false; + } + + //transfer amount, it will take tax, burn, liquidity fee + _tokenTransfer(from,to,amount,takeFee); + } + + function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap { + // split the contract balance into halves + uint256 half = contractTokenBalance.div(2); + uint256 otherHalf = contractTokenBalance.sub(half); + + // capture the contract's current ETH balance. + // this is so that we can capture exactly the amount of ETH that the + // swap creates, and not make the liquidity event include any ETH that + // has been manually sent to the contract + uint256 initialBalance = address(this).balance; + + // swap tokens for ETH + swapTokensForEth(half); // <- this breaks the ETH -> HATE swap when swap+liquify is triggered + + // how much ETH did we just swap into? + uint256 newBalance = address(this).balance.sub(initialBalance); + + // add liquidity to uniswap + addLiquidity(otherHalf, newBalance); + + emit SwapAndLiquify(half, newBalance, otherHalf); + } + + 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), + block.timestamp + ); + } + + function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private { + // approve token transfer to cover all possible scenarios + _approve(address(this), address(uniswapV2Router), tokenAmount); + + // add the liquidity + uniswapV2Router.addLiquidityETH{value: ethAmount}( + address(this), + tokenAmount, + 0, // slippage is unavoidable + 0, // slippage is unavoidable + owner(), + block.timestamp + ); + } + + //this method is responsible for taking all fee, if takeFee is true + function _tokenTransfer(address sender, address recipient, uint256 amount,bool takeFee) private { + 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); + } + + + + +} diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/SpaceGoat.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/SpaceGoat.sol new file mode 100644 index 0000000000..b204542e2a --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/SpaceGoat.sol @@ -0,0 +1,1198 @@ +/** + *Submitted for verification at BscScan.com on 2021-05-21 +*/ + +pragma solidity ^0.6.11; +// SPDX-License-Identifier: Unlicensed +interface IERC20 { + + 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; + } +} + +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; + } +} + + +/** + * @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) { + // 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); + } + + /** + * @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); + } + } + } +} + +/** + * @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; + address private _previousOwner; + + 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 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; + } +} + + +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 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 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; +} + +contract SpaceGoat is Context, IERC20, Ownable { + using SafeMath for uint256; + using Address for address; + address contractOwner; + + 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; + + uint256 private MAX = ~uint256(0); + uint256 private constant _tTotal = 1000000000 * 10**6 * 10**9; + uint256 private _rTotal = (MAX - (MAX % _tTotal)); + uint256 private _tFeeTotal; + + string private constant _name = "SPACEGOAT TOKEN"; + string private constant _symbol = "SGT"; + uint8 private constant _decimals = 9; + + uint256 public _taxFee = 4; //4% holder distribution + uint256 private _previousTaxFee = _taxFee; + + uint256 public _liquidityFee = 2; //2% liquidity pool + uint256 private _previousLiquidityFee = _liquidityFee; + + uint256 public _charityPlusExmarketFee = 4; //1% charity fee + 1% exMarket fee + 2% burn wallet + uint256 private _previouscharityPlusExmarketFee = _charityPlusExmarketFee; + + address payable private _charityWallet = 0xD42e4F076721b92f1B87E4A0B3d0bAa354b2a7d8; // Charity Fund + address payable private _exMarketWallet = 0x8fc0b3DC7690a3CD96a0033B48073ACa7c859713; // Exchange + Marketing Fund + address payable private _burnWallet = 0x0000000000000000000000000000000000000001; + + IUniswapV2Router02 public uniswapV2Router; + address public uniswapV2Pair; + address private pancake = 0x10ED43C718714eb63d5aA57B78B54704E256024E; + + bool inSwapAndLiquify; + bool public swapAndLiquifyEnabled = false; + bool public burnEnabled = true; + + uint256 public _maxTxAmount = 5000000 * 10**6 * 10**9; + uint256 private constant numTokensSellToAddToLiquidity = 500000 * 10**6 * 10**9; + + event MinTokensBeforeSwapUpdated(uint256 minTokensBeforeSwap); + event SwapAndLiquifyEnabledUpdated(bool enabled); + event BurnEnabledUpdated(bool enabled); + event SwapAndLiquify( + uint256 tokensSwapped, + uint256 ethReceived, + uint256 tokensIntoLiquidity + ); + + modifier lockTheSwap { + inSwapAndLiquify = true; + _; + inSwapAndLiquify = false; + } + + constructor () public { + _rOwned[_msgSender()] = _rTotal; + + setRouterAddress(pancake); + //exclude owner and this contract from fee + _isExcludedFromFee[owner()] = true; + _isExcludedFromFee[address(this)] = true; + + contractOwner = msg.sender; + + emit Transfer(address(0), _msgSender(), _tTotal); + } + + function name() public pure returns (string memory) { + return _name; + } + + function symbol() public pure returns (string memory) { + return _symbol; + } + + + function decimals() public pure returns (uint8) { + return _decimals; + } + + function totalSupply() public 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) public override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + function allowance(address owner, address spender) public view override returns (uint256) { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) public override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + function transferFrom(address sender, address recipient, uint256 amount) public 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 isExcludedFromReward(address account) public view returns (bool) { + return _isExcluded[account]; + } + + function totalFees() public view returns (uint256) { + return _tFeeTotal; + } + + function deliver(uint256 tAmount) public onlyOwner{ + 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) public 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) public onlyOwner() { + require(!_isExcluded[account], "Account is not 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 not 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 _transferBothExcluded(address sender, address recipient, uint256 tAmount) private { + (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity, uint tcharityPlusExmarketFee) = _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); + _takecharityPlusExmarketFee(tcharityPlusExmarketFee); + emit Transfer(sender, recipient, tTransferAmount); + } + + function excludeFromFee(address account) public onlyOwner { + _isExcludedFromFee[account] = true; + } + + function includeInFee(address account) public onlyOwner { + _isExcludedFromFee[account] = false; + } + + function setTaxFeePercent(uint256 taxFee) external onlyOwner() { + _taxFee = taxFee; + } + + function setLiquidityFeePercent(uint256 liquidityFee) external onlyOwner() { + _liquidityFee = liquidityFee; + } + + function setRouterAddress(address newRouter) public onlyOwner() { + //Thank you FreezyEx + // current v2 = 0x10ED43C718714eb63d5aA57B78B54704E256024E + // Address the version change problem PancakeSwap + IUniswapV2Router02 _newPancakeRouter = IUniswapV2Router02(newRouter); + uniswapV2Pair = IUniswapV2Factory(_newPancakeRouter.factory()).createPair(address(this), _newPancakeRouter.WETH()); + uniswapV2Router = _newPancakeRouter; + } + + function setcharityPlusExmarketFee(uint256 charityPlusExmarketFee) external onlyOwner() { + _charityPlusExmarketFee = charityPlusExmarketFee; + } + + function setCharityWallet(address payable charityWalletAddress) external onlyOwner() { + _charityWallet = charityWalletAddress; + } + + function setExMarketWallet(address payable exMarketWalletAddress) external onlyOwner() { + _exMarketWallet = exMarketWalletAddress; + } + + function setBurnEnabled(bool _enabled) public onlyOwner { + burnEnabled = _enabled; + emit BurnEnabledUpdated(_enabled); + } + + function setMaxTxPercent(uint256 maxTxPercent) external onlyOwner() { + _maxTxAmount = _tTotal.mul(maxTxPercent).div( + 10**2 + ); + } + + function setSwapAndLiquifyEnabled(bool _enabled) public onlyOwner { + swapAndLiquifyEnabled = _enabled; + emit SwapAndLiquifyEnabledUpdated(_enabled); + } + + //to receive BNB from uniswapV2Router when swapping + receive() external payable {} + + 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) { + (uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity, uint256 tcharityPlusExmarketFee) = _getTValues(tAmount); + (uint256 rAmount, uint256 rTransferAmount, uint256 rFee) = _getRValues(tAmount, tFee, tLiquidity, tcharityPlusExmarketFee, _getRate()); + return (rAmount, rTransferAmount, rFee, tTransferAmount, tFee, tLiquidity, tcharityPlusExmarketFee); + } + + function _getTValues(uint256 tAmount) private view returns (uint256, uint256, uint256, uint256) { + uint256 tFee = calculateTaxFee(tAmount); + uint256 tLiquidity = calculateLiquidityFee(tAmount); + uint tcharityPlusExmarketFee; + if(msg.sender != contractOwner){ + tcharityPlusExmarketFee = calculateCharityPlusExmarketFee(tAmount); + }else{ + tcharityPlusExmarketFee=0; + } + uint256 tTransferAmount = tAmount.sub(tFee).sub(tLiquidity).sub(tcharityPlusExmarketFee); + return (tTransferAmount, tFee, tLiquidity, tcharityPlusExmarketFee); + } + + function _getRValues(uint256 tAmount, uint256 tFee, uint256 tLiquidity, uint256 tcharityPlusExmarketFee, uint256 currentRate) private view returns (uint256, uint256, uint256) { + uint256 rAmount = tAmount.mul(currentRate); + uint256 rFee = tFee.mul(currentRate); + uint256 rLiquidity = tLiquidity.mul(currentRate); + uint256 rcharityPlusExmarketFee; + + if(msg.sender != contractOwner){ + rcharityPlusExmarketFee = tcharityPlusExmarketFee.mul(currentRate); + }else{ + rcharityPlusExmarketFee=0; + } + + uint256 rTransferAmount = rAmount.sub(rFee).sub(rLiquidity).sub(rcharityPlusExmarketFee); + 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 _takecharityPlusExmarketFee(uint256 tcharityPlusExmarketFee) private{ + + uint256 currentRate = _getRate(); + uint256 rCharityFee = tcharityPlusExmarketFee.mul(currentRate); + uint256 burnWallet; + if (burnEnabled) { + //we want to stop splitting fees + burnWallet = rCharityFee.div(2); + } + + uint256 charity = rCharityFee.sub(burnWallet); + uint256 charityFee = charity.div(2); + uint256 exMarketFee = charity.sub(charityFee); + + + _rOwned[_charityWallet] = _rOwned[_charityWallet].add(charityFee); + _rOwned[_exMarketWallet] = _rOwned[_exMarketWallet].add(exMarketFee); + _rOwned[_burnWallet] = _rOwned[_burnWallet].add(burnWallet); + + if(_isExcluded[address(this)]) + _tOwned[address(this)] = _tOwned[address(this)].add(tcharityPlusExmarketFee); + } + + 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 calculateCharityPlusExmarketFee(uint256 _amount) private view returns (uint256) { + return _amount.mul(_charityPlusExmarketFee).div( + 10**2 + ); + } + + function removeAllFee() private { + if(_taxFee == 0 && _liquidityFee == 0 && _charityPlusExmarketFee == 0) return; + + _previousTaxFee = _taxFee; + _previousLiquidityFee = _liquidityFee; + _previouscharityPlusExmarketFee = _charityPlusExmarketFee; + + _taxFee = 0; + _liquidityFee = 0; + _charityPlusExmarketFee=0; + } + + function restoreAllFee() private { + _taxFee = _previousTaxFee; + _liquidityFee = _previousLiquidityFee; + _charityPlusExmarketFee = _previouscharityPlusExmarketFee; + } + + function isExcludedFromFee(address account) public view returns(bool) { + return _isExcludedFromFee[account]; + } + + 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."); + + // is the token balance of this contract address over the min number of + // tokens that we need to initiate a swap + liquidity lock? + // also, don't get caught in a circular liquidity event. + // also, don't swap & liquify if sender is uniswap pair. + uint256 contractTokenBalance = balanceOf(address(this)); + + if(contractTokenBalance >= _maxTxAmount) + { + contractTokenBalance = _maxTxAmount; + } + + bool overMinTokenBalance = contractTokenBalance >= numTokensSellToAddToLiquidity; + + if ( + overMinTokenBalance && + !inSwapAndLiquify && + from != uniswapV2Pair && + swapAndLiquifyEnabled + ) { + contractTokenBalance = numTokensSellToAddToLiquidity; + //add liquidity + swapAndLiquify(contractTokenBalance); + } + + //indicates if fee should be deducted from transfer + bool takeFee = true; + + //if any account belongs to _isExcludedFromFee account then remove the fee + if(_isExcludedFromFee[from] || _isExcludedFromFee[to]){ + takeFee = false; + } + + //transfer amount, it will take tax, burn, liquidity fee + _tokenTransfer(from,to,amount,takeFee); + + } + + function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap { + // split the contract balance into halves + uint256 half = contractTokenBalance.div(2); + uint256 otherHalf = contractTokenBalance.sub(half); + + uint256 initialBalance = address(this).balance; + + // swap tokens for BNB + swapTokensForBNB(half); + + // how much BNB did we just swap into? + uint256 newBalance = address(this).balance.sub(initialBalance); + + // add liquidity to pancakeswap + addLiquidity(otherHalf, newBalance); + + emit SwapAndLiquify(half, newBalance, otherHalf); + } + + + function swapTokensForBNB(uint256 tokenAmount) private { + // generate the pancakeswap pair path of token -> BNB + 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 BNB + path, + address(this), + block.timestamp + ); + } + + function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private returns(uint){ + // approve token transfer to cover all possible scenarios + _approve(address(this), address(uniswapV2Router), tokenAmount); + + // add the liquidity + uniswapV2Router.addLiquidityETH{value: ethAmount}( + address(this), + tokenAmount, + 0, // slippage is unavoidable + 0, // slippage is unavoidable + owner(), + block.timestamp + ); + return tokenAmount; + } + + //this method is responsible for taking all fee, if takeFee is true + function _tokenTransfer(address sender, address recipient, uint256 amount,bool takeFee) private { + 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]) { + _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, uint256 tcharityPlusExmarketFee) = _getValues(tAmount); + _rOwned[sender] = _rOwned[sender].sub(rAmount); + _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount); + _takeLiquidity(tLiquidity); + _reflectFee(rFee, tFee); + _takecharityPlusExmarketFee(tcharityPlusExmarketFee); + + 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,uint256 tcharityPlusExmarketFee) = _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); + _takecharityPlusExmarketFee(tcharityPlusExmarketFee); + + 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,uint256 tcharityPlusExmarketFee) = _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); + _takecharityPlusExmarketFee(tcharityPlusExmarketFee); + + emit Transfer(sender, recipient, tTransferAmount); + } + +} \ No newline at end of file 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 0000000000..a302c1b986 --- /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 0000000000..c47b78800b --- /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 0000000000..253659a960 --- /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 0000000000..8982cf6036 --- /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 0000000000..5b84cb545b --- /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 0000000000..9665776c92 --- /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 0000000000..fcedef50a3 --- /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 0000000000..263b489328 --- /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 0000000000..8b1966a723 --- /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 0000000000..0cafb46d79 --- /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 0000000000..ff92616b7d --- /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 0000000000..7ad3b4c3bb --- /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 0000000000..03065db5ba --- /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 0000000000..0c6d6ebfd3 --- /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 0000000000..3e9f2e9dd8 --- /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 0000000000..83d5de8744 --- /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 0000000000..9d0f9d8ad6 --- /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 0000000000..bf84cf99b1 --- /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 0000000000..22d5f2a6e4 --- /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 0000000000..f91d42b357 --- /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 0000000000..c82b91cd6a --- /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/efvault/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol new file mode 100644 index 0000000000..affb7cdcb9 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol) + +pragma solidity ^0.8.0; + +import "../utils/ContextUpgradeable.sol"; +import "../proxy/utils/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 onlyInitializing { + __Ownable_init_unchained(); + } + + function __Ownable_init_unchained() internal onlyInitializing { + _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); + } + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[49] private __gap; +} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol new file mode 100644 index 0000000000..3aa7535dac --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (proxy/utils/Initializable.sol) + +pragma solidity ^0.8.2; + +import "../../utils/AddressUpgradeable.sol"; + +/** + * @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 proxied contracts do not make use of 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. + * + * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be + * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in + * case an upgrade adds a module that needs to be initialized. + * + * For example: + * + * [.hljs-theme-light.nopadding] + * ``` + * contract MyToken is ERC20Upgradeable { + * function initialize() initializer public { + * __ERC20_init("MyToken", "MTK"); + * } + * } + * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { + * function initializeV2() reinitializer(2) public { + * __ERC20Permit_init("MyToken"); + * } + * } + * ``` + * + * 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. + * + * [CAUTION] + * ==== + * Avoid leaving a contract uninitialized. + * + * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation + * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke + * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: + * + * [.hljs-theme-light.nopadding] + * ``` + * /// @custom:oz-upgrades-unsafe-allow constructor + * constructor() { + * _disableInitializers(); + * } + * ``` + * ==== + */ +abstract contract Initializable { + /** + * @dev Indicates that the contract has been initialized. + * @custom:oz-retyped-from bool + */ + uint8 private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Triggered when the contract has been initialized or reinitialized. + */ + event Initialized(uint8 version); + + /** + * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, + * `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`. + */ + modifier initializer() { + bool isTopLevelCall = !_initializing; + require( + (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1), + "Initializable: contract is already initialized" + ); + _initialized = 1; + if (isTopLevelCall) { + _initializing = true; + } + _; + if (isTopLevelCall) { + _initializing = false; + emit Initialized(1); + } + } + + /** + * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the + * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be + * used to initialize parent contracts. + * + * `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original + * initialization step. This is essential to configure modules that are added through upgrades and that require + * initialization. + * + * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in + * a contract, executing them in the right order is up to the developer or operator. + */ + modifier reinitializer(uint8 version) { + require(!_initializing && _initialized < version, "Initializable: contract is already initialized"); + _initialized = version; + _initializing = true; + _; + _initializing = false; + emit Initialized(version); + } + + /** + * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the + * {initializer} and {reinitializer} modifiers, directly or indirectly. + */ + modifier onlyInitializing() { + require(_initializing, "Initializable: contract is not initializing"); + _; + } + + /** + * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. + * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized + * to any version. It is recommended to use this to lock implementation contracts that are designed to be called + * through proxies. + */ + function _disableInitializers() internal virtual { + require(!_initializing, "Initializable: contract is initializing"); + if (_initialized < type(uint8).max) { + _initialized = type(uint8).max; + emit Initialized(type(uint8).max); + } + } +} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol new file mode 100644 index 0000000000..2a34a67ab1 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol) + +pragma solidity ^0.8.0; +import "../proxy/utils/Initializable.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 ReentrancyGuardUpgradeable is Initializable { + // 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; + + function __ReentrancyGuard_init() internal onlyInitializing { + __ReentrancyGuard_init_unchained(); + } + + function __ReentrancyGuard_init_unchained() internal onlyInitializing { + _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 making 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 This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[49] private __gap; +} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol new file mode 100644 index 0000000000..f544f0a515 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.0; + +import "./IERC20Upgradeable.sol"; +import "./extensions/IERC20MetadataUpgradeable.sol"; +import "../../utils/ContextUpgradeable.sol"; +import "../../proxy/utils/Initializable.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 ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable { + 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. + */ + function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing { + __ERC20_init_unchained(name_, symbol_); + } + + function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing { + _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 {} + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[45] private __gap; +} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol new file mode 100644 index 0000000000..f72ebe9676 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +// 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 IERC20Upgradeable { + /** + * @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); +} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol new file mode 100644 index 0000000000..381d8ea2a1 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.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 "../IERC20Upgradeable.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20MetadataUpgradeable is IERC20Upgradeable { + /** + * @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); +} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol new file mode 100644 index 0000000000..bd5751322e --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in + * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by + * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't + * need to send a transaction, and thus is not required to hold Ether at all. + */ +interface IERC20PermitUpgradeable { + /** + * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, + * given ``owner``'s signed approval. + * + * IMPORTANT: The same issues {IERC20-approve} has related to transaction + * ordering also apply here. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `deadline` must be a timestamp in the future. + * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` + * over the EIP712-formatted function arguments. + * - the signature must use ``owner``'s current nonce (see {nonces}). + * + * For more information on the signature format, see the + * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP + * section]. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + * @dev Returns the current nonce for `owner`. This value must be + * included whenever a signature is generated for {permit}. + * + * Every successful call to {permit} increases ``owner``'s nonce by one. This + * prevents a signature from being used multiple times. + */ + function nonces(address owner) external view returns (uint256); + + /** + * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32); +} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol new file mode 100644 index 0000000000..65d2fc1a7a --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/utils/SafeERC20.sol) + +pragma solidity ^0.8.0; + +import "../IERC20Upgradeable.sol"; +import "../extensions/draft-IERC20PermitUpgradeable.sol"; +import "../../../utils/AddressUpgradeable.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 SafeERC20Upgradeable { + using AddressUpgradeable for address; + + function safeTransfer( + IERC20Upgradeable token, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom( + IERC20Upgradeable 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( + IERC20Upgradeable 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( + IERC20Upgradeable 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( + IERC20Upgradeable 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)); + } + } + + function safePermit( + IERC20PermitUpgradeable token, + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) internal { + uint256 nonceBefore = token.nonces(owner); + token.permit(owner, spender, value, deadline, v, r, s); + uint256 nonceAfter = token.nonces(owner); + require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed"); + } + + /** + * @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(IERC20Upgradeable 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"); + } + } +} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol new file mode 100644 index 0000000000..124513390b --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol) + +pragma solidity ^0.8.1; + +/** + * @dev Collection of functions related to the address type + */ +library AddressUpgradeable { + /** + * @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 + * ==== + * + * [IMPORTANT] + * ==== + * You shouldn't rely on `isContract` to protect against flash loan attacks! + * + * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets + * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract + * constructor. + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize/address.code.length, which returns 0 + // for contracts in construction, since the code is only stored at the end + // of the constructor execution. + + return account.code.length > 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 Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason using the provided one. + * + * _Available since v4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal 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 + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol new file mode 100644 index 0000000000..481064e415 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.0; +import "../proxy/utils/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 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 onlyInitializing { + } + + function __Context_init_unchained() internal onlyInitializing { + } + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[50] private __gap; +} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts/utils/math/SafeMath.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts/utils/math/SafeMath.sol new file mode 100644 index 0000000000..550f0e779b --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts/utils/math/SafeMath.sol @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: MIT +// 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; + } + } +} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/interfaces/IController.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/interfaces/IController.sol new file mode 100644 index 0000000000..3fd249cf0c --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/interfaces/IController.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IController { + function totalAssets() external view returns (uint256); + + function deposit(uint256 _amount) external returns (uint256); + + function withdraw(uint256 _amount, address _receiver) external returns (uint256, uint256); +} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/interfaces/IVault.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/interfaces/IVault.sol new file mode 100644 index 0000000000..d722b91984 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/interfaces/IVault.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IVault { + function deposit(uint256 assets, address receiver) external payable returns (uint256 shares); + + function mint(uint256 amount, address account) external; +} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/utils/TransferHelper.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/utils/TransferHelper.sol new file mode 100644 index 0000000000..041c08a263 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/utils/TransferHelper.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.0; + + +// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false +library TransferHelper { + function safeApprove( + address token, + address to, + uint256 value + ) internal { + // bytes4(keccak256(bytes('approve(address,uint256)'))); + (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value)); + require(success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper: APPROVE_FAILED"); + } + + /** + Transfer Util for both Ether and ERC20 + */ + function safeTransfer( + address token, + address to, + uint256 value + ) internal { + if (address(token) == address(0)) { + safeTransferETH(to, value); + } else { + safeTransferToken(address(token), to, value); + } + } + + function safeTransferToken( + address token, + address to, + uint256 value + ) internal { + // bytes4(keccak256(bytes('transfer(address,uint256)'))); + (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value)); + require(success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper: TRANSFER_FAILED"); + } + + function safeTransferFrom( + address token, + address from, + address to, + uint256 value + ) internal { + // bytes4(keccak256(bytes('transferFrom(address,address,uint256)'))); + (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value)); + require(success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper: TRANSFER_FROM_FAILED"); + } + + function safeTransferETH(address to, uint256 value) internal { + (bool success, ) = to.call{value: value}(new bytes(0)); + require(success, "TransferHelper: ETH_TRANSFER_FAILED"); + } +} 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 0000000000..6fd9202117 --- /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/BConst.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BConst.sol new file mode 100644 index 0000000000..7f2b3f25d3 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BConst.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.6.0; + + +/************************************************************************************************ +Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BConst.sol + +This source code has been modified from the original, which was copied from the github repository +at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. + +Subject to the GPL-3.0 license +*************************************************************************************************/ + + +contract BConst { + uint256 public constant VERSION_NUMBER = 1; + +/* --- Weight Updates --- */ + + // Minimum time passed between each weight update for a token. + uint256 internal constant WEIGHT_UPDATE_DELAY = 30 minutes; + + // Maximum percent by which a weight can adjust at a time + // relative to the current weight. + // The number of iterations needed to move from weight A to weight B is the floor of: + // (A > B): (ln(A) - ln(B)) / ln(1.01) + // (B > A): (ln(A) - ln(B)) / ln(0.99) + uint256 internal constant WEIGHT_CHANGE_PCT = BONE/100; + + uint256 internal constant BONE = 10**18; + + uint256 internal constant MIN_BOUND_TOKENS = 2; + uint256 internal constant MAX_BOUND_TOKENS = 10; + + // Minimum swap fee. + uint256 internal constant MIN_FEE = BONE / 10**6; + // Maximum swap or exit fee. + uint256 internal constant MAX_FEE = BONE / 10; + // Actual exit fee. + uint256 internal constant EXIT_FEE = 5e15; + + // Default total of all desired weights. Can differ by up to BONE. + uint256 internal constant DEFAULT_TOTAL_WEIGHT = BONE * 25; + // Minimum weight for any token (1/100). + uint256 internal constant MIN_WEIGHT = BONE / 4; + uint256 internal constant MAX_WEIGHT = BONE * 25; + // Maximum total weight. + uint256 internal constant MAX_TOTAL_WEIGHT = BONE * 27; + // Minimum balance for a token (only applied at initialization) + uint256 internal constant MIN_BALANCE = BONE / 10**12; + // Initial pool tokens + uint256 internal constant INIT_POOL_SUPPLY = BONE * 100; + + uint256 internal constant MIN_BPOW_BASE = 1 wei; + uint256 internal constant MAX_BPOW_BASE = (2 * BONE) - 1 wei; + uint256 internal constant BPOW_PRECISION = BONE / 10**10; + + // Maximum ratio of input tokens to balance for swaps. + uint256 internal constant MAX_IN_RATIO = BONE / 2; + // Maximum ratio of output tokens to balance for swaps. + uint256 internal constant MAX_OUT_RATIO = (BONE / 3) + 1 wei; +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BMath.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BMath.sol new file mode 100644 index 0000000000..47593291a2 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BMath.sol @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.6.0; + +import "./BNum.sol"; + + +/************************************************************************************************ +Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BMath.sol + +This source code has been modified from the original, which was copied from the github repository +at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. + +Subject to the GPL-3.0 license +*************************************************************************************************/ + + +contract BMath is BConst, BNum { + /********************************************************************************************** + // calcSpotPrice // + // sP = spotPrice // + // bI = tokenBalanceIn ( bI / wI ) 1 // + // bO = tokenBalanceOut sP = ----------- * ---------- // + // wI = tokenWeightIn ( bO / wO ) ( 1 - sF ) // + // wO = tokenWeightOut // + // sF = swapFee // + **********************************************************************************************/ + function calcSpotPrice( + uint256 tokenBalanceIn, + uint256 tokenWeightIn, + uint256 tokenBalanceOut, + uint256 tokenWeightOut, + uint256 swapFee + ) internal pure returns (uint256 spotPrice) { + uint256 numer = bdiv(tokenBalanceIn, tokenWeightIn); + uint256 denom = bdiv(tokenBalanceOut, tokenWeightOut); + uint256 ratio = bdiv(numer, denom); + uint256 scale = bdiv(BONE, bsub(BONE, swapFee)); + return (spotPrice = bmul(ratio, scale)); + } + + /********************************************************************************************** + // calcOutGivenIn // + // aO = tokenAmountOut // + // bO = tokenBalanceOut // + // bI = tokenBalanceIn / / bI \ (wI / wO) \ // + // aI = tokenAmountIn aO = bO * | 1 - | -------------------------- | ^ | // + // wI = tokenWeightIn \ \ ( bI + ( aI * ( 1 - sF )) / / // + // wO = tokenWeightOut // + // sF = swapFee // + **********************************************************************************************/ + function calcOutGivenIn( + uint256 tokenBalanceIn, + uint256 tokenWeightIn, + uint256 tokenBalanceOut, + uint256 tokenWeightOut, + uint256 tokenAmountIn, + uint256 swapFee + ) internal pure returns (uint256 tokenAmountOut) { + uint256 weightRatio = bdiv(tokenWeightIn, tokenWeightOut); + uint256 adjustedIn = bsub(BONE, swapFee); + adjustedIn = bmul(tokenAmountIn, adjustedIn); + uint256 y = bdiv(tokenBalanceIn, badd(tokenBalanceIn, adjustedIn)); + uint256 foo = bpow(y, weightRatio); + uint256 bar = bsub(BONE, foo); + tokenAmountOut = bmul(tokenBalanceOut, bar); + return tokenAmountOut; + } + + /********************************************************************************************** + // calcInGivenOut // + // aI = tokenAmountIn // + // bO = tokenBalanceOut / / bO \ (wO / wI) \ // + // bI = tokenBalanceIn bI * | | ------------ | ^ - 1 | // + // aO = tokenAmountOut aI = \ \ ( bO - aO ) / / // + // wI = tokenWeightIn -------------------------------------------- // + // wO = tokenWeightOut ( 1 - sF ) // + // sF = swapFee // + **********************************************************************************************/ + function calcInGivenOut( + uint256 tokenBalanceIn, + uint256 tokenWeightIn, + uint256 tokenBalanceOut, + uint256 tokenWeightOut, + uint256 tokenAmountOut, + uint256 swapFee + ) internal pure returns (uint256 tokenAmountIn) { + uint256 weightRatio = bdiv(tokenWeightOut, tokenWeightIn); + uint256 diff = bsub(tokenBalanceOut, tokenAmountOut); + uint256 y = bdiv(tokenBalanceOut, diff); + uint256 foo = bpow(y, weightRatio); + foo = bsub(foo, BONE); + tokenAmountIn = bsub(BONE, swapFee); + tokenAmountIn = bdiv(bmul(tokenBalanceIn, foo), tokenAmountIn); + return tokenAmountIn; + } + + /********************************************************************************************** + // calcPoolOutGivenSingleIn // + // pAo = poolAmountOut / \ // + // tAi = tokenAmountIn /// / // wI \ \\ \ wI \ // + // wI = tokenWeightIn //| tAi *| 1 - || 1 - -- | * sF || + tBi \ -- \ // + // tW = totalWeight pAo=|| \ \ \\ tW / // | ^ tW | * pS - pS // + // tBi = tokenBalanceIn \\ ------------------------------------- / / // + // pS = poolSupply \\ tBi / / // + // sF = swapFee \ / // + **********************************************************************************************/ + function calcPoolOutGivenSingleIn( + uint256 tokenBalanceIn, + uint256 tokenWeightIn, + uint256 poolSupply, + uint256 totalWeight, + uint256 tokenAmountIn, + uint256 swapFee + ) internal pure returns (uint256 poolAmountOut) { + // Charge the trading fee for the proportion of tokenAi + /// which is implicitly traded to the other pool tokens. + // That proportion is (1- weightTokenIn) + // tokenAiAfterFee = tAi * (1 - (1-weightTi) * poolFee); + uint256 normalizedWeight = bdiv(tokenWeightIn, totalWeight); + uint256 zaz = bmul(bsub(BONE, normalizedWeight), swapFee); + uint256 tokenAmountInAfterFee = bmul(tokenAmountIn, bsub(BONE, zaz)); + + uint256 newTokenBalanceIn = badd(tokenBalanceIn, tokenAmountInAfterFee); + uint256 tokenInRatio = bdiv(newTokenBalanceIn, tokenBalanceIn); + + // uint newPoolSupply = (ratioTi ^ weightTi) * poolSupply; + uint256 poolRatio = bpow(tokenInRatio, normalizedWeight); + uint256 newPoolSupply = bmul(poolRatio, poolSupply); + poolAmountOut = bsub(newPoolSupply, poolSupply); + return poolAmountOut; + } + + /********************************************************************************************** + // calcSingleInGivenPoolOut // + // tAi = tokenAmountIn //(pS + pAo)\ / 1 \\ // + // pS = poolSupply || --------- | ^ | --------- || * bI - bI // + // pAo = poolAmountOut \\ pS / \(wI / tW)// // + // bI = balanceIn tAi = -------------------------------------------- // + // wI = weightIn / wI \ // + // tW = totalWeight | 1 - ---- | * sF // + // sF = swapFee \ tW / // + **********************************************************************************************/ + function calcSingleInGivenPoolOut( + uint256 tokenBalanceIn, + uint256 tokenWeightIn, + uint256 poolSupply, + uint256 totalWeight, + uint256 poolAmountOut, + uint256 swapFee + ) internal pure returns (uint256 tokenAmountIn) { + uint256 normalizedWeight = bdiv(tokenWeightIn, totalWeight); + uint256 newPoolSupply = badd(poolSupply, poolAmountOut); + uint256 poolRatio = bdiv(newPoolSupply, poolSupply); + + //uint newBalTi = poolRatio^(1/weightTi) * balTi; + uint256 boo = bdiv(BONE, normalizedWeight); + uint256 tokenInRatio = bpow(poolRatio, boo); + uint256 newTokenBalanceIn = bmul(tokenInRatio, tokenBalanceIn); + uint256 tokenAmountInAfterFee = bsub(newTokenBalanceIn, tokenBalanceIn); + // Do reverse order of fees charged in joinswap_ExternAmountIn, this way + // ``` pAo == joinswap_ExternAmountIn(Ti, joinswap_PoolAmountOut(pAo, Ti)) ``` + //uint tAi = tAiAfterFee / (1 - (1-weightTi) * swapFee) ; + uint256 zar = bmul(bsub(BONE, normalizedWeight), swapFee); + tokenAmountIn = bdiv(tokenAmountInAfterFee, bsub(BONE, zar)); + return tokenAmountIn; + } + + /********************************************************************************************** + // calcSingleOutGivenPoolIn // + // tAo = tokenAmountOut / / \\ // + // bO = tokenBalanceOut / // pS - (pAi * (1 - eF)) \ / 1 \ \\ // + // pAi = poolAmountIn | bO - || ----------------------- | ^ | --------- | * b0 || // + // ps = poolSupply \ \\ pS / \(wO / tW)/ // // + // wI = tokenWeightIn tAo = \ \ // // + // tW = totalWeight / / wO \ \ // + // sF = swapFee * | 1 - | 1 - ---- | * sF | // + // eF = exitFee \ \ tW / / // + **********************************************************************************************/ + function calcSingleOutGivenPoolIn( + uint256 tokenBalanceOut, + uint256 tokenWeightOut, + uint256 poolSupply, + uint256 totalWeight, + uint256 poolAmountIn, + uint256 swapFee + ) internal pure returns (uint256 tokenAmountOut) { + uint256 normalizedWeight = bdiv(tokenWeightOut, totalWeight); + // charge exit fee on the pool token side + // pAiAfterExitFee = pAi*(1-exitFee) + uint256 poolAmountInAfterExitFee = bmul(poolAmountIn, bsub(BONE, EXIT_FEE)); + uint256 newPoolSupply = bsub(poolSupply, poolAmountInAfterExitFee); + uint256 poolRatio = bdiv(newPoolSupply, poolSupply); + + // newBalTo = poolRatio^(1/weightTo) * balTo; + uint256 tokenOutRatio = bpow(poolRatio, bdiv(BONE, normalizedWeight)); + uint256 newTokenBalanceOut = bmul(tokenOutRatio, tokenBalanceOut); + + uint256 tokenAmountOutBeforeSwapFee = bsub( + tokenBalanceOut, + newTokenBalanceOut + ); + + // charge swap fee on the output token side + //uint tAo = tAoBeforeSwapFee * (1 - (1-weightTo) * swapFee) + uint256 zaz = bmul(bsub(BONE, normalizedWeight), swapFee); + tokenAmountOut = bmul(tokenAmountOutBeforeSwapFee, bsub(BONE, zaz)); + return tokenAmountOut; + } + + /********************************************************************************************** + // calcPoolInGivenSingleOut // + // pAi = poolAmountIn // / tAo \\ / wO \ \ // + // bO = tokenBalanceOut // | bO - -------------------------- |\ | ---- | \ // + // tAo = tokenAmountOut pS - || \ 1 - ((1 - (tO / tW)) * sF)/ | ^ \ tW / * pS | // + // ps = poolSupply \\ -----------------------------------/ / // + // wO = tokenWeightOut pAi = \\ bO / / // + // tW = totalWeight ------------------------------------------------------------- // + // sF = swapFee ( 1 - eF ) // + // eF = exitFee // + **********************************************************************************************/ + function calcPoolInGivenSingleOut( + uint256 tokenBalanceOut, + uint256 tokenWeightOut, + uint256 poolSupply, + uint256 totalWeight, + uint256 tokenAmountOut, + uint256 swapFee + ) internal pure returns (uint256 poolAmountIn) { + // charge swap fee on the output token side + uint256 normalizedWeight = bdiv(tokenWeightOut, totalWeight); + //uint tAoBeforeSwapFee = tAo / (1 - (1-weightTo) * swapFee) ; + uint256 zoo = bsub(BONE, normalizedWeight); + uint256 zar = bmul(zoo, swapFee); + uint256 tokenAmountOutBeforeSwapFee = bdiv(tokenAmountOut, bsub(BONE, zar)); + + uint256 newTokenBalanceOut = bsub( + tokenBalanceOut, + tokenAmountOutBeforeSwapFee + ); + uint256 tokenOutRatio = bdiv(newTokenBalanceOut, tokenBalanceOut); + + //uint newPoolSupply = (ratioTo ^ weightTo) * poolSupply; + uint256 poolRatio = bpow(tokenOutRatio, normalizedWeight); + uint256 newPoolSupply = bmul(poolRatio, poolSupply); + uint256 poolAmountInAfterExitFee = bsub(poolSupply, newPoolSupply); + + // charge exit fee on the pool token side + // pAi = pAiAfterExitFee/(1-exitFee) + poolAmountIn = bdiv(poolAmountInAfterExitFee, bsub(BONE, EXIT_FEE)); + return poolAmountIn; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BNum.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BNum.sol new file mode 100644 index 0000000000..8dfe7689d0 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BNum.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.6.0; + +import "./BConst.sol"; + + +/************************************************************************************************ +Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BNum.sol + +This source code has been modified from the original, which was copied from the github repository +at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. + +Subject to the GPL-3.0 license +*************************************************************************************************/ + + +contract BNum is BConst { + function btoi(uint256 a) internal pure returns (uint256) { + return a / BONE; + } + + function bfloor(uint256 a) internal pure returns (uint256) { + return btoi(a) * BONE; + } + + function badd(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "ERR_ADD_OVERFLOW"); + return c; + } + + function bsub(uint256 a, uint256 b) internal pure returns (uint256) { + (uint256 c, bool flag) = bsubSign(a, b); + require(!flag, "ERR_SUB_UNDERFLOW"); + return c; + } + + function bsubSign(uint256 a, uint256 b) + internal + pure + returns (uint256, bool) + { + if (a >= b) { + return (a - b, false); + } else { + return (b - a, true); + } + } + + function bmul(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c0 = a * b; + require(a == 0 || c0 / a == b, "ERR_MUL_OVERFLOW"); + uint256 c1 = c0 + (BONE / 2); + require(c1 >= c0, "ERR_MUL_OVERFLOW"); + uint256 c2 = c1 / BONE; + return c2; + } + + function bdiv(uint256 a, uint256 b) internal pure returns (uint256) { + require(b != 0, "ERR_DIV_ZERO"); + uint256 c0 = a * BONE; + require(a == 0 || c0 / a == BONE, "ERR_DIV_INTERNAL"); // bmul overflow + uint256 c1 = c0 + (b / 2); + require(c1 >= c0, "ERR_DIV_INTERNAL"); // badd require + uint256 c2 = c1 / b; + return c2; + } + + // DSMath.wpow + function bpowi(uint256 a, uint256 n) internal pure returns (uint256) { + uint256 z = n % 2 != 0 ? a : BONE; + + for (n /= 2; n != 0; n /= 2) { + a = bmul(a, a); + + if (n % 2 != 0) { + z = bmul(z, a); + } + } + return z; + } + + // Compute b^(e.w) by splitting it into (b^e)*(b^0.w). + // Use `bpowi` for `b^e` and `bpowK` for k iterations + // of approximation of b^0.w + function bpow(uint256 base, uint256 exp) internal pure returns (uint256) { + require(base >= MIN_BPOW_BASE, "ERR_BPOW_BASE_TOO_LOW"); + require(base <= MAX_BPOW_BASE, "ERR_BPOW_BASE_TOO_HIGH"); + + uint256 whole = bfloor(exp); + uint256 remain = bsub(exp, whole); + + uint256 wholePow = bpowi(base, btoi(whole)); + + if (remain == 0) { + return wholePow; + } + + uint256 partialResult = bpowApprox(base, remain, BPOW_PRECISION); + return bmul(wholePow, partialResult); + } + + function bpowApprox( + uint256 base, + uint256 exp, + uint256 precision + ) internal pure returns (uint256) { + // term 0: + uint256 a = exp; + (uint256 x, bool xneg) = bsubSign(base, BONE); + uint256 term = BONE; + uint256 sum = term; + bool negative = false; + + // term(k) = numer / denom + // = (product(a - i - 1, i=1-->k) * x^k) / (k!) + // each iteration, multiply previous term by (a-(k-1)) * x / k + // continue until term is less than precision + for (uint256 i = 1; term >= precision; i++) { + uint256 bigK = i * BONE; + (uint256 c, bool cneg) = bsubSign(a, bsub(bigK, BONE)); + term = bmul(term, bmul(c, x)); + term = bdiv(term, bigK); + if (term == 0) break; + + if (xneg) negative = !negative; + if (cneg) negative = !negative; + if (negative) { + sum = bsub(sum, term); + } else { + sum = badd(sum, term); + } + } + + return sum; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BToken.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BToken.sol new file mode 100644 index 0000000000..1053a7f002 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BToken.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.6.0; + +import "./BNum.sol"; + + +/************************************************************************************************ +Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BToken.sol + +This source code has been modified from the original, which was copied from the github repository +at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. + +Subject to the GPL-3.0 license +*************************************************************************************************/ + + +// Highly opinionated token implementation +interface IERC20 { + event Approval(address indexed src, address indexed dst, uint256 amt); + event Transfer(address indexed src, address indexed dst, uint256 amt); + + function name() external view returns (string memory); + + function symbol() external view returns (string memory); + + function decimals() external view returns (uint8); + + function totalSupply() external view returns (uint256); + + function balanceOf(address whom) external view returns (uint256); + + function allowance(address src, address dst) external view returns (uint256); + + function approve(address dst, uint256 amt) external returns (bool); + + function transfer(address dst, uint256 amt) external returns (bool); + + function transferFrom( + address src, + address dst, + uint256 amt + ) external returns (bool); +} + + +contract BTokenBase is BNum { + mapping(address => uint256) internal _balance; + mapping(address => mapping(address => uint256)) internal _allowance; + uint256 internal _totalSupply; + + event Approval(address indexed src, address indexed dst, uint256 amt); + event Transfer(address indexed src, address indexed dst, uint256 amt); + + function _mint(uint256 amt) internal { + _balance[address(this)] = badd(_balance[address(this)], amt); + _totalSupply = badd(_totalSupply, amt); + emit Transfer(address(0), address(this), amt); + } + + function _burn(uint256 amt) internal { + require(_balance[address(this)] >= amt, "ERR_INSUFFICIENT_BAL"); + _balance[address(this)] = bsub(_balance[address(this)], amt); + _totalSupply = bsub(_totalSupply, amt); + emit Transfer(address(this), address(0), amt); + } + + function _move( + address src, + address dst, + uint256 amt + ) internal { + require(_balance[src] >= amt, "ERR_INSUFFICIENT_BAL"); + _balance[src] = bsub(_balance[src], amt); + _balance[dst] = badd(_balance[dst], amt); + emit Transfer(src, dst, amt); + } + + function _push(address to, uint256 amt) internal { + _move(address(this), to, amt); + } + + function _pull(address from, uint256 amt) internal { + _move(from, address(this), amt); + } +} + + +contract BToken is BTokenBase, IERC20 { + uint8 private constant DECIMALS = 18; + string private _name; + string private _symbol; + + function _initializeToken(string memory name, string memory symbol) internal { + require( + bytes(_name).length == 0 && + bytes(name).length != 0 && + bytes(symbol).length != 0, + "ERR_BTOKEN_INITIALIZED" + ); + _name = name; + _symbol = symbol; + } + + function name() + external + override + view + returns (string memory) + { + return _name; + } + + function symbol() + external + override + view + returns (string memory) + { + return _symbol; + } + + function decimals() + external + override + view + returns (uint8) + { + return DECIMALS; + } + + function allowance(address src, address dst) + external + override + view + returns (uint256) + { + return _allowance[src][dst]; + } + + function balanceOf(address whom) external override view returns (uint256) { + return _balance[whom]; + } + + function totalSupply() public override view returns (uint256) { + return _totalSupply; + } + + function approve(address dst, uint256 amt) external override returns (bool) { + _allowance[msg.sender][dst] = amt; + emit Approval(msg.sender, dst, amt); + return true; + } + + function increaseApproval(address dst, uint256 amt) external returns (bool) { + _allowance[msg.sender][dst] = badd(_allowance[msg.sender][dst], amt); + emit Approval(msg.sender, dst, _allowance[msg.sender][dst]); + return true; + } + + function decreaseApproval(address dst, uint256 amt) external returns (bool) { + uint256 oldValue = _allowance[msg.sender][dst]; + if (amt > oldValue) { + _allowance[msg.sender][dst] = 0; + } else { + _allowance[msg.sender][dst] = bsub(oldValue, amt); + } + emit Approval(msg.sender, dst, _allowance[msg.sender][dst]); + return true; + } + + function transfer(address dst, uint256 amt) external override returns (bool) { + _move(msg.sender, dst, amt); + return true; + } + + function transferFrom( + address src, + address dst, + uint256 amt + ) external override returns (bool) { + require( + msg.sender == src || amt <= _allowance[src][msg.sender], + "ERR_BTOKEN_BAD_CALLER" + ); + _move(src, dst, amt); + if (msg.sender != src && _allowance[src][msg.sender] != uint256(-1)) { + _allowance[src][msg.sender] = bsub(_allowance[src][msg.sender], amt); + emit Approval(msg.sender, dst, _allowance[src][msg.sender]); + } + return true; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BeefyVault.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BeefyVault.sol new file mode 100644 index 0000000000..d855711d9c --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BeefyVault.sol @@ -0,0 +1,941 @@ +/** + *Submitted for verification at BscScan.com on 2020-10-27 +*/ + +// SPDX-License-Identifier: MIT +// File: @openzeppelin/contracts/GSN/Context.sol + + +pragma solidity ^0.6.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/token/ERC20/IERC20.sol + + +pragma solidity ^0.6.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; + +/** + * @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; + } +} + +// File: @openzeppelin/contracts/utils/Address.sol + + +pragma solidity ^0.6.2; + +/** + * @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); + } + } + } +} + +// File: @openzeppelin/contracts/token/ERC20/ERC20.sol + + +pragma solidity ^0.6.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 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 { + using SafeMath for uint256; + using Address for address; + + mapping (address => uint256) private _balances; + + mapping (address => mapping (address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + uint8 private _decimals; + + /** + * @dev Sets the values for {name} and {symbol}, initializes {decimals} with + * a default value of 18. + * + * To select a different value for {decimals}, use {_setupDecimals}. + * + * All three of these values are immutable: they can only be set once during + * construction. + */ + constructor (string memory name, string memory symbol) public { + _name = name; + _symbol = symbol; + _decimals = 18; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view 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 {_setupDecimals} is + * called. + * + * 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 returns (uint8) { + return _decimals; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view 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); + _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); + 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].add(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) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); + 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); + + _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); + _balances[recipient] = _balances[recipient].add(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 = _totalSupply.add(amount); + _balances[account] = _balances[account].add(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); + + _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); + _totalSupply = _totalSupply.sub(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 Sets {decimals} to a value other than the default one of 18. + * + * WARNING: This function should only be called from the constructor. Most + * applications that interact with token contracts will not expect + * {decimals} to ever change, and may work incorrectly if it does. + */ + function _setupDecimals(uint8 decimals_) internal { + _decimals = decimals_; + } + + /** + * @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: @openzeppelin/contracts/token/ERC20/SafeERC20.sol + + +pragma solidity ^0.6.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/BIFI/interfaces/beefy/IStrategy.sol + + +pragma solidity ^0.6.0; + +interface IStrategy { + function want() external view returns (address); + function deposit() external; + function withdraw(uint256) external; + function balanceOf() external view returns (uint256); +} + +// File: contracts/BIFI/vaults/BeefyVault.sol + + +pragma solidity ^0.6.0; + + + + + + +/** + * @dev Implementation of a vault to deposit funds for yield optimizing. + * This is the contract that receives funds and that users interface with. + * The yield optimizing strategy itself is implemented in a separate 'Strategy.sol' contract. + */ +contract BeefyVault is ERC20 { + using SafeERC20 for IERC20; + using Address for address; + using SafeMath for uint256; + + IERC20 public token; + address public strategy; + + /** + * @dev Sets the value of {token} to the token that the vault will + * hold as underlying value. It initializes the vault's own 'moo' token. + * This token is minted when someone does a deposit. It is burned in order + * to withdraw the corresponding portion of the underlying assets. + * @param _token the token to maximize. + * @param _strategy the address of the strategy. + * @param _name the name of the vault token. + * @param _symbol the symbol of the vault token. + */ + constructor (address _token, address _strategy, string memory _name, string memory _symbol) public ERC20( + string(abi.encodePacked(_name)), + string(abi.encodePacked(_symbol)) + ) { + token = IERC20(_token); + strategy = _strategy; + } + + /** + * @dev It calculates the total underlying value of {token} held by the system. + * It takes into account the vault contract balance, the strategy contract balance + * and the balance deployed in other contracts as part of the strategy. + */ + function balance() public view returns (uint) { + return token.balanceOf(address(this)) + .add(IStrategy(strategy).balanceOf()); + } + + /** + * @dev Custom logic in here for how much the vault allows to be borrowed. + * We return 100% of tokens for now. Under certain conditions we might + * want to keep some of the system funds at hand in the vault, instead + * of putting them to work. + */ + function available() public view returns (uint) { + return token.balanceOf(address(this)); + } + + /** + * @dev Function for various UIs to display the current value of one of our yield tokens. + * Returns an uint with 18 decimals of how much underlying asset one vault share represents. + */ + function getPricePerFullShare() public view returns (uint) { + return balance().mul(1e18).div(totalSupply()); + } + + /** + * @dev A helper function to call deposit() with all the sender's funds. + */ + function depositAll() external { + deposit(token.balanceOf(msg.sender)); + } + + /** + * @dev The entrypoint of funds into the system. People deposit with this function + * into the vault. The vault is then in charge of sending funds into the strategy. + */ + function deposit(uint _amount) public { + uint _pool = balance(); + uint _before = token.balanceOf(address(this)); + token.safeTransferFrom(msg.sender, address(this), _amount); + uint _after = token.balanceOf(address(this)); + _amount = _after.sub(_before); // Additional check for deflationary tokens + uint shares = 0; + if (totalSupply() == 0) { + shares = _amount; + } else { + shares = (_amount.mul(totalSupply())).div(_pool); + } + _mint(msg.sender, shares); + + earn(); + } + + /** + * @dev Function to send funds into the strategy and put them to work. It's primarily called + * by the vault's deposit() function. + */ + function earn() public { + uint _bal = available(); + token.safeTransfer(strategy, _bal); + IStrategy(strategy).deposit(); + } + + /** + * @dev A helper function to call withdraw() with all the sender's funds. + */ + function withdrawAll() external { + withdraw(balanceOf(msg.sender)); + } + + /** + * @dev Function to exit the system. The vault will withdraw the required tokens + * from the strategy and pay up the token holder. A proportional number of IOU + * tokens are burned in the process. + */ + function withdraw(uint _shares) public { + uint _withdraw = (balance().mul(_shares)).div(totalSupply()); + _burn(msg.sender, _shares); + + uint _before = token.balanceOf(address(this)); + IStrategy(strategy).withdraw(_withdraw); + uint _after = token.balanceOf(address(this)); + uint _diff = _after.sub(_before); + + token.safeTransfer(msg.sender, _diff); + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/ICompLikeToken.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/ICompLikeToken.sol new file mode 100644 index 0000000000..f98b034d68 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/ICompLikeToken.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + + +interface ICompLikeToken { + function delegate(address delegatee) external; +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/IIndexPool.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/IIndexPool.sol new file mode 100644 index 0000000000..3a499c389b --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/IIndexPool.sol @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.6.0; +pragma experimental ABIEncoderV2; + + +interface IIndexPool { + /** + * @dev Token record data structure + * @param bound is token bound to pool + * @param ready has token been initialized + * @param lastDenormUpdate timestamp of last denorm change + * @param denorm denormalized weight + * @param desiredDenorm desired denormalized weight (used for incremental changes) + * @param index index of address in tokens array + * @param balance token balance + */ + struct Record { + bool bound; + bool ready; + uint40 lastDenormUpdate; + uint96 denorm; + uint96 desiredDenorm; + uint8 index; + uint256 balance; + } + +/* ========== EVENTS ========== */ + + /** @dev Emitted when tokens are swapped. */ + event LOG_SWAP( + address indexed caller, + address indexed tokenIn, + address indexed tokenOut, + uint256 tokenAmountIn, + uint256 tokenAmountOut + ); + + /** @dev Emitted when underlying tokens are deposited for pool tokens. */ + event LOG_JOIN( + address indexed caller, + address indexed tokenIn, + uint256 tokenAmountIn + ); + + /** @dev Emitted when pool tokens are burned for underlying. */ + event LOG_EXIT( + address indexed caller, + address indexed tokenOut, + uint256 tokenAmountOut + ); + + /** @dev Emitted when a token's weight updates. */ + event LOG_DENORM_UPDATED(address indexed token, uint256 newDenorm); + + /** @dev Emitted when a token's desired weight is set. */ + event LOG_DESIRED_DENORM_SET(address indexed token, uint256 desiredDenorm); + + /** @dev Emitted when a token is unbound from the pool. */ + event LOG_TOKEN_REMOVED(address token); + + /** @dev Emitted when a token is unbound from the pool. */ + event LOG_TOKEN_ADDED( + address indexed token, + uint256 desiredDenorm, + uint256 minimumBalance + ); + + /** @dev Emitted when a token's minimum balance is updated. */ + event LOG_MINIMUM_BALANCE_UPDATED(address token, uint256 minimumBalance); + + /** @dev Emitted when a token reaches its minimum balance. */ + event LOG_TOKEN_READY(address indexed token); + + /** @dev Emitted when public trades are enabled. */ + event LOG_PUBLIC_SWAP_ENABLED(); + + /** @dev Emitted when the swap fee is updated. */ + event LOG_SWAP_FEE_UPDATED(uint256 swapFee); + + /** @dev Emitted when exit fee recipient is updated. */ + event LOG_EXIT_FEE_RECIPIENT_UPDATED(address exitFeeRecipient); + + /** @dev Emitted when controller is updated. */ + event LOG_CONTROLLER_UPDATED(address exitFeeRecipient); + + function configure( + address controller, + string calldata name, + string calldata symbol, + address exitFeeRecipient + ) external; + + function initialize( + address[] calldata tokens, + uint256[] calldata balances, + uint96[] calldata denorms, + address tokenProvider, + address unbindHandler + ) external; + + function setSwapFee(uint256 swapFee) external; + + function delegateCompLikeToken(address token, address delegatee) external; + + function setExitFeeRecipient(address exitFeeRecipient) external; + + function setController(address controller) external; + + function reweighTokens( + address[] calldata tokens, + uint96[] calldata desiredDenorms + ) external; + + function reindexTokens( + address[] calldata tokens, + uint96[] calldata desiredDenorms, + uint256[] calldata minimumBalances + ) external; + + function setMinimumBalance(address token, uint256 minimumBalance) external; + + function joinPool(uint256 poolAmountOut, uint256[] calldata maxAmountsIn) external; + + function joinswapExternAmountIn( + address tokenIn, + uint256 tokenAmountIn, + uint256 minPoolAmountOut + ) external returns (uint256/* poolAmountOut */); + + 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 gulp(address token) external; + + function swapExactAmountIn( + address tokenIn, + uint256 tokenAmountIn, + address tokenOut, + uint256 minAmountOut, + uint256 maxPrice + ) external returns (uint256/* tokenAmountOut */, uint256/* spotPriceAfter */); + + function swapExactAmountOut( + address tokenIn, + uint256 maxAmountIn, + address tokenOut, + uint256 tokenAmountOut, + uint256 maxPrice + ) external returns (uint256 /* tokenAmountIn */, uint256 /* spotPriceAfter */); + + function isPublicSwap() external view returns (bool); + + function getSwapFee() external view returns (uint256/* swapFee */); + + function getExitFee() external view returns (uint256/* exitFee */); + + function getController() external view returns (address); + + function getExitFeeRecipient() external view returns (address); + + function isBound(address t) external view returns (bool); + + function getNumTokens() external view returns (uint256); + + function getCurrentTokens() external view returns (address[] memory tokens); + + function getCurrentDesiredTokens() external view returns (address[] memory tokens); + + function getDenormalizedWeight(address token) external view returns (uint256/* denorm */); + + function getTokenRecord(address token) external view returns (Record memory record); + + function extrapolatePoolValueFromToken() external view returns (address/* token */, uint256/* extrapolatedValue */); + + function getTotalDenormalizedWeight() external view returns (uint256); + + function getBalance(address token) external view returns (uint256); + + function getMinimumBalance(address token) external view returns (uint256); + + function getUsedBalance(address token) external view returns (uint256); + + function getSpotPrice(address tokenIn, address tokenOut) external view returns (uint256); +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/IndexPool.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/IndexPool.sol new file mode 100644 index 0000000000..647c59bdb3 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/IndexPool.sol @@ -0,0 +1,1368 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.6.0; +pragma experimental ABIEncoderV2; + +/* ========== Internal Inheritance ========== */ +import "./BToken.sol"; +import "./BMath.sol"; + +/* ========== Internal Interfaces ========== */ +import "./IIndexPool.sol"; +import "./ICompLikeToken.sol"; + + +/************************************************************************************************ +Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol + +This source code has been modified from the original, which was copied from the github repository +at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. + +Subject to the GPL-3.0 license +*************************************************************************************************/ + + +contract IndexPool is BToken, BMath, IIndexPool { +/* ========== Modifiers ========== */ + + modifier _lock_ { + require(!_mutex, "ERR_REENTRY"); + _mutex = true; + _; + _mutex = false; + } + + modifier _viewlock_() { + require(!_mutex, "ERR_REENTRY"); + _; + } + + modifier _control_ { + require(msg.sender == _controller, "ERR_NOT_CONTROLLER"); + _; + } + + modifier _public_ { + require(_publicSwap, "ERR_NOT_PUBLIC"); + _; + } + +/* ========== Storage ========== */ + + bool internal _mutex; + + // Account with CONTROL role. Able to modify the swap fee, + // adjust token weights, bind and unbind tokens and lock + // public swaps & joins. + address internal _controller; + + // Contract that handles unbound tokens. + TokenUnbindHandler internal _unbindHandler; + + // True if PUBLIC can call SWAP & JOIN functions + bool internal _publicSwap; + + // `setSwapFee` requires CONTROL + uint256 internal _swapFee; + + // Array of underlying tokens in the pool. + address[] internal _tokens; + + // Internal records of the pool's underlying tokens + mapping(address => Record) internal _records; + + // Total denormalized weight of the pool. + uint256 internal _totalWeight; + + // Minimum balances for tokens which have been added without the + // requisite initial balance. + mapping(address => uint256) internal _minimumBalances; + + // Recipient for exit fees + address internal _exitFeeRecipient; + +/* ========== Controls ========== */ + + /** + * @dev Sets the controller address and the token name & symbol. + * + * Note: This saves on storage costs for multi-step pool deployment. + * + * @param controller Controller of the pool + * @param name Name of the pool token + * @param symbol Symbol of the pool token + * @param exitFeeRecipient Address that receives exit fees + */ + function configure( + address controller, + string calldata name, + string calldata symbol, + address exitFeeRecipient + ) external override { + require(_controller == address(0), "ERR_CONFIGURED"); + require(controller != address(0) && exitFeeRecipient != address(0), "ERR_NULL_ADDRESS"); + _controller = controller; + // default fee is 2% + _swapFee = BONE / 50; + _exitFeeRecipient = exitFeeRecipient; + _initializeToken(name, symbol); + } + + /** + * @dev Sets up the initial assets for the pool. + * + * Note: `tokenProvider` must have approved the pool to transfer the + * corresponding `balances` of `tokens`. + * + * @param tokens Underlying tokens to initialize the pool with + * @param balances Initial balances to transfer + * @param denorms Initial denormalized weights for the tokens + * @param tokenProvider Address to transfer the balances from + */ + function initialize( + address[] calldata tokens, + uint256[] calldata balances, + uint96[] calldata denorms, + address tokenProvider, + address unbindHandler + ) + external + override + _control_ + { + require(_tokens.length == 0, "ERR_INITIALIZED"); + uint256 len = tokens.length; + require(len >= MIN_BOUND_TOKENS, "ERR_MIN_TOKENS"); + require(len <= MAX_BOUND_TOKENS, "ERR_MAX_TOKENS"); + require(balances.length == len && denorms.length == len, "ERR_ARR_LEN"); + uint256 totalWeight = 0; + for (uint256 i = 0; i < len; i++) { + address token = tokens[i]; + uint96 denorm = denorms[i]; + uint256 balance = balances[i]; + require(denorm >= MIN_WEIGHT, "ERR_MIN_WEIGHT"); + require(denorm <= MAX_WEIGHT, "ERR_MAX_WEIGHT"); + require(balance >= MIN_BALANCE, "ERR_MIN_BALANCE"); + _records[token] = Record({ + bound: true, + ready: true, + lastDenormUpdate: uint40(now), + denorm: denorm, + desiredDenorm: denorm, + index: uint8(i), + balance: balance + }); + _tokens.push(token); + totalWeight = badd(totalWeight, denorm); + _pullUnderlying(token, tokenProvider, balance); + } + require(totalWeight <= MAX_TOTAL_WEIGHT, "ERR_MAX_TOTAL_WEIGHT"); + _totalWeight = totalWeight; + _publicSwap = true; + emit LOG_PUBLIC_SWAP_ENABLED(); + _mintPoolShare(INIT_POOL_SUPPLY); + _pushPoolShare(tokenProvider, INIT_POOL_SUPPLY); + _unbindHandler = TokenUnbindHandler(unbindHandler); + } + + /** + * @dev Set the swap fee. + * Note: Swap fee must be between 0.0001% and 10% + */ + function setSwapFee(uint256 swapFee) external override _control_ { + require(swapFee >= MIN_FEE && swapFee <= MAX_FEE, "ERR_INVALID_FEE"); + _swapFee = swapFee; + emit LOG_SWAP_FEE_UPDATED(swapFee); + } + + /** + * @dev Delegate a comp-like governance token to an address + * specified by the controller. + */ + function delegateCompLikeToken(address token,address delegatee) + external + override + _control_ + { + ICompLikeToken(token).delegate(delegatee); + } + + /** + * @dev Set the exit fee recipient address. + */ + function setExitFeeRecipient(address exitFeeRecipient) external override _control_ { + require(exitFeeRecipient != address(0), "ERR_NULL_ADDRESS"); + _exitFeeRecipient = exitFeeRecipient; + emit LOG_EXIT_FEE_RECIPIENT_UPDATED(exitFeeRecipient); + } + + /** + * @dev Set the controller address + */ + function setController(address controller) external override _control_ { + require(controller != address(0), "ERR_NULL_ADDRESS"); + _controller = controller; + emit LOG_CONTROLLER_UPDATED(controller); + } + +/* ========== Token Management Actions ========== */ + + /** + * @dev Sets the desired weights for the pool tokens, which + * will be adjusted over time as they are swapped. + * + * Note: This does not check for duplicate tokens or that the total + * of the desired weights is equal to the target total weight (25). + * Those assumptions should be met in the controller. Further, the + * provided tokens should only include the tokens which are not set + * for removal. + */ + function reweighTokens( + address[] calldata tokens, + uint96[] calldata desiredDenorms + ) + external + override + _lock_ + _control_ + { + require(desiredDenorms.length == tokens.length, "ERR_ARR_LEN"); + for (uint256 i = 0; i < tokens.length; i++) + _setDesiredDenorm(tokens[i], desiredDenorms[i]); + } + + /** + * @dev Update the underlying assets held by the pool and their associated + * weights. Tokens which are not currently bound will be gradually added + * as they are swapped in to reach the provided minimum balances, which must + * be an amount of tokens worth the minimum weight of the total pool value. + * If a currently bound token is not received in this call, the token's + * desired weight will be set to 0. + */ + function reindexTokens( + address[] calldata tokens, + uint96[] calldata desiredDenorms, + uint256[] calldata minimumBalances + ) + external + override + _lock_ + _control_ + { + require( + desiredDenorms.length == tokens.length && minimumBalances.length == tokens.length, + "ERR_ARR_LEN" + ); + // This size may not be the same as the input size, as it is possible + // to temporarily exceed the index size while tokens are being phased in + // or out. + uint256 tLen = _tokens.length; + bool[] memory receivedIndices = new bool[](tLen); + // We need to read token records in two separate loops, so + // write them to memory to avoid duplicate storage reads. + Record[] memory records = new Record[](tokens.length); + // Read all the records from storage and mark which of the existing tokens + // were represented in the reindex call. + for (uint256 i = 0; i < tokens.length; i++) { + records[i] = _records[tokens[i]]; + if (records[i].bound) receivedIndices[records[i].index] = true; + } + // If any bound tokens were not sent in this call, set their desired weights to 0. + for (uint256 i = 0; i < tLen; i++) { + if (!receivedIndices[i]) { + _setDesiredDenorm(_tokens[i], 0); + } + } + for (uint256 i = 0; i < tokens.length; i++) { + address token = tokens[i]; + // If an input weight is less than the minimum weight, use that instead. + uint96 denorm = desiredDenorms[i]; + if (denorm < MIN_WEIGHT) denorm = uint96(MIN_WEIGHT); + if (!records[i].bound) { + // If the token is not bound, bind it. + _bind(token, minimumBalances[i], denorm); + } else { + _setDesiredDenorm(token, denorm); + } + } + } + + /** + * @dev Updates the minimum balance for an uninitialized token. + * This becomes useful if a token's external price significantly + * rises after being bound, since the pool can not send a token + * out until it reaches the minimum balance. + */ + function setMinimumBalance( + address token, + uint256 minimumBalance + ) + external + override + _control_ + { + Record storage record = _records[token]; + require(record.bound, "ERR_NOT_BOUND"); + require(!record.ready, "ERR_READY"); + _minimumBalances[token] = minimumBalance; + emit LOG_MINIMUM_BALANCE_UPDATED(token, minimumBalance); + } + +/* ========== Liquidity Provider Actions ========== */ + + /** + * @dev Mint new pool tokens by providing the proportional amount of each + * underlying token's balance relative to the proportion of pool tokens minted. + * + * For any underlying tokens which are not initialized, the caller must provide + * the proportional share of the minimum balance for the token rather than the + * actual balance. + * + * @param poolAmountOut Amount of pool tokens to mint + * @param maxAmountsIn Maximum amount of each token to pay in the same + * order as the pool's _tokens list. + */ + function joinPool(uint256 poolAmountOut, uint256[] calldata maxAmountsIn) + external + override + _lock_ + _public_ + { + uint256 poolTotal = totalSupply(); + uint256 ratio = bdiv(poolAmountOut, poolTotal); + require(ratio != 0, "ERR_MATH_APPROX"); + require(maxAmountsIn.length == _tokens.length, "ERR_ARR_LEN"); + + for (uint256 i = 0; i < maxAmountsIn.length; i++) { + address t = _tokens[i]; + (Record memory record, uint256 realBalance) = _getInputToken(t); + uint256 tokenAmountIn = bmul(ratio, record.balance); + require(tokenAmountIn != 0, "ERR_MATH_APPROX"); + require(tokenAmountIn <= maxAmountsIn[i], "ERR_LIMIT_IN"); + _updateInputToken(t, record, badd(realBalance, tokenAmountIn)); + emit LOG_JOIN(msg.sender, t, tokenAmountIn); + _pullUnderlying(t, msg.sender, tokenAmountIn); + } + _mintPoolShare(poolAmountOut); + _pushPoolShare(msg.sender, poolAmountOut); + } + + /** + * @dev Pay `tokenAmountIn` of `tokenIn` to mint at least `minPoolAmountOut` + * pool tokens. + * + * The pool implicitly swaps `(1- weightTokenIn) * tokenAmountIn` to the other + * underlying tokens. Thus a swap fee is charged against the input tokens. + * + * @param tokenIn Token to send the pool + * @param tokenAmountIn Exact amount of `tokenIn` to pay + * @param minPoolAmountOut Minimum amount of pool tokens to mint + * @return poolAmountOut - Amount of pool tokens minted + */ + function joinswapExternAmountIn( + address tokenIn, + uint256 tokenAmountIn, + uint256 minPoolAmountOut + ) + external + override + _lock_ + _public_ + returns (uint256/* poolAmountOut */) + { + (Record memory inRecord, uint256 realInBalance) = _getInputToken(tokenIn); + + require(tokenAmountIn != 0, "ERR_ZERO_IN"); + + require( + tokenAmountIn <= bmul(inRecord.balance, MAX_IN_RATIO), + "ERR_MAX_IN_RATIO" + ); + + uint256 poolAmountOut = calcPoolOutGivenSingleIn( + inRecord.balance, + inRecord.denorm, + _totalSupply, + _totalWeight, + tokenAmountIn, + _swapFee + ); + + require(poolAmountOut >= minPoolAmountOut, "ERR_LIMIT_OUT"); + + _updateInputToken(tokenIn, inRecord, badd(realInBalance, tokenAmountIn)); + + emit LOG_JOIN(msg.sender, tokenIn, tokenAmountIn); + + _mintPoolShare(poolAmountOut); + _pushPoolShare(msg.sender, poolAmountOut); + _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); + + return poolAmountOut; + } + + /** + * @dev Pay up to `maxAmountIn` of `tokenIn` to mint exactly `poolAmountOut`. + * + * The pool implicitly swaps `(1- weightTokenIn) * tokenAmountIn` to the other + * underlying tokens. Thus a swap fee is charged against the input tokens. + * + * @param tokenIn Token to send the pool + * @param poolAmountOut Exact amount of pool tokens to mint + * @param maxAmountIn Maximum amount of `tokenIn` to pay + * @return tokenAmountIn - Amount of `tokenIn` paid + */ + function joinswapPoolAmountOut( + address tokenIn, + uint256 poolAmountOut, + uint256 maxAmountIn + ) + external + override + _lock_ + _public_ + returns (uint256/* tokenAmountIn */) + { + (Record memory inRecord, uint256 realInBalance) = _getInputToken(tokenIn); + + uint256 tokenAmountIn = calcSingleInGivenPoolOut( + inRecord.balance, + inRecord.denorm, + _totalSupply, + _totalWeight, + poolAmountOut, + _swapFee + ); + + require(tokenAmountIn != 0, "ERR_MATH_APPROX"); + require(tokenAmountIn <= maxAmountIn, "ERR_LIMIT_IN"); + + require( + tokenAmountIn <= bmul(inRecord.balance, MAX_IN_RATIO), + "ERR_MAX_IN_RATIO" + ); + + _updateInputToken(tokenIn, inRecord, badd(realInBalance, tokenAmountIn)); + + emit LOG_JOIN(msg.sender, tokenIn, tokenAmountIn); + + _mintPoolShare(poolAmountOut); + _pushPoolShare(msg.sender, poolAmountOut); + _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); + + return tokenAmountIn; + } + + /** + * @dev Burns `poolAmountIn` pool tokens in exchange for the amounts of each + * underlying token's balance proportional to the ratio of tokens burned to + * total pool supply. The amount of each token transferred to the caller must + * be greater than or equal to the associated minimum output amount from the + * `minAmountsOut` array. + * + * @param poolAmountIn Exact amount of pool tokens to burn + * @param minAmountsOut Minimum amount of each token to receive, in the same + * order as the pool's _tokens list. + */ + function exitPool(uint256 poolAmountIn, uint256[] calldata minAmountsOut) + external + override + _lock_ + { + require(minAmountsOut.length == _tokens.length, "ERR_ARR_LEN"); + uint256 poolTotal = totalSupply(); + uint256 exitFee = bmul(poolAmountIn, EXIT_FEE); + uint256 pAiAfterExitFee = bsub(poolAmountIn, exitFee); + uint256 ratio = bdiv(pAiAfterExitFee, poolTotal); + require(ratio != 0, "ERR_MATH_APPROX"); + + _pullPoolShare(msg.sender, poolAmountIn); + _pushPoolShare(_exitFeeRecipient, exitFee); + _burnPoolShare(pAiAfterExitFee); + for (uint256 i = 0; i < minAmountsOut.length; i++) { + address t = _tokens[i]; + Record memory record = _records[t]; + if (record.ready) { + uint256 tokenAmountOut = bmul(ratio, record.balance); + require(tokenAmountOut != 0, "ERR_MATH_APPROX"); + require(tokenAmountOut >= minAmountsOut[i], "ERR_LIMIT_OUT"); + + _records[t].balance = bsub(record.balance, tokenAmountOut); + emit LOG_EXIT(msg.sender, t, tokenAmountOut); + _pushUnderlying(t, msg.sender, tokenAmountOut); + } else { + // If the token is not initialized, it can not exit the pool. + require(minAmountsOut[i] == 0, "ERR_OUT_NOT_READY"); + } + } + } + + /** + * @dev Burns `poolAmountIn` pool tokens in exchange for at least `minAmountOut` + * of `tokenOut`. Returns the number of tokens sent to the caller. + * + * The pool implicitly burns the tokens for all underlying tokens and swaps them + * to the desired output token. A swap fee is charged against the output tokens. + * + * @param tokenOut Token to receive + * @param poolAmountIn Exact amount of pool tokens to burn + * @param minAmountOut Minimum amount of `tokenOut` to receive + * @return tokenAmountOut - Amount of `tokenOut` received + */ + function exitswapPoolAmountIn( + address tokenOut, + uint256 poolAmountIn, + uint256 minAmountOut + ) + external + override + _lock_ + returns (uint256/* tokenAmountOut */) + { + Record memory outRecord = _getOutputToken(tokenOut); + + uint256 tokenAmountOut = calcSingleOutGivenPoolIn( + outRecord.balance, + outRecord.denorm, + _totalSupply, + _totalWeight, + poolAmountIn, + _swapFee + ); + + require(tokenAmountOut >= minAmountOut, "ERR_LIMIT_OUT"); + + require( + tokenAmountOut <= bmul(outRecord.balance, MAX_OUT_RATIO), + "ERR_MAX_OUT_RATIO" + ); + + _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); + _records[tokenOut].balance = bsub(outRecord.balance, tokenAmountOut); + _decreaseDenorm(outRecord, tokenOut); + uint256 exitFee = bmul(poolAmountIn, EXIT_FEE); + + emit LOG_EXIT(msg.sender, tokenOut, tokenAmountOut); + + _pullPoolShare(msg.sender, poolAmountIn); + _burnPoolShare(bsub(poolAmountIn, exitFee)); + _pushPoolShare(_exitFeeRecipient, exitFee); + + return tokenAmountOut; + } + + /** + * @dev Burn up to `maxPoolAmountIn` for exactly `tokenAmountOut` of `tokenOut`. + * Returns the number of pool tokens burned. + * + * The pool implicitly burns the tokens for all underlying tokens and swaps them + * to the desired output token. A swap fee is charged against the output tokens. + * + * @param tokenOut Token to receive + * @param tokenAmountOut Exact amount of `tokenOut` to receive + * @param maxPoolAmountIn Maximum amount of pool tokens to burn + * @return poolAmountIn - Amount of pool tokens burned + */ + function exitswapExternAmountOut( + address tokenOut, + uint256 tokenAmountOut, + uint256 maxPoolAmountIn + ) + external + override + _lock_ + returns (uint256/* poolAmountIn */) + { + Record memory outRecord = _getOutputToken(tokenOut); + require( + tokenAmountOut <= bmul(outRecord.balance, MAX_OUT_RATIO), + "ERR_MAX_OUT_RATIO" + ); + + uint256 poolAmountIn = calcPoolInGivenSingleOut( + outRecord.balance, + outRecord.denorm, + _totalSupply, + _totalWeight, + tokenAmountOut, + _swapFee + ); + + require(poolAmountIn != 0, "ERR_MATH_APPROX"); + require(poolAmountIn <= maxPoolAmountIn, "ERR_LIMIT_IN"); + + _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); + _records[tokenOut].balance = bsub(outRecord.balance, tokenAmountOut); + _decreaseDenorm(outRecord, tokenOut); + + uint256 exitFee = bmul(poolAmountIn, EXIT_FEE); + + emit LOG_EXIT(msg.sender, tokenOut, tokenAmountOut); + + _pullPoolShare(msg.sender, poolAmountIn); + _burnPoolShare(bsub(poolAmountIn, exitFee)); + _pushPoolShare(_exitFeeRecipient, exitFee); + + return poolAmountIn; + } + +/* ========== Other ========== */ + + /** + * @dev Absorb any tokens that have been sent to the pool. + * If the token is not bound, it will be sent to the unbound + * token handler. + */ + function gulp(address token) external override _lock_ { + Record storage record = _records[token]; + uint256 balance = IERC20(token).balanceOf(address(this)); + if (record.bound) { + if (!record.ready) { + uint256 minimumBalance = _minimumBalances[token]; + if (balance >= minimumBalance) { + _minimumBalances[token] = 0; + record.ready = true; + emit LOG_TOKEN_READY(token); + uint256 additionalBalance = bsub(balance, minimumBalance); + uint256 balRatio = bdiv(additionalBalance, minimumBalance); + uint96 newDenorm = uint96(badd(MIN_WEIGHT, bmul(MIN_WEIGHT, balRatio))); + record.denorm = newDenorm; + record.lastDenormUpdate = uint40(now); + _totalWeight = badd(_totalWeight, newDenorm); + emit LOG_DENORM_UPDATED(token, record.denorm); + } + } + _records[token].balance = balance; + } else { + _pushUnderlying(token, address(_unbindHandler), balance); + _unbindHandler.handleUnbindToken(token, balance); + } + } + +/* ========== Token Swaps ========== */ + + /** + * @dev Execute a token swap with a specified amount of input + * tokens and a minimum amount of output tokens. + * + * Note: Will revert if `tokenOut` is uninitialized. + * + * @param tokenIn Token to swap in + * @param tokenAmountIn Exact amount of `tokenIn` to swap in + * @param tokenOut Token to swap out + * @param minAmountOut Minimum amount of `tokenOut` to receive + * @param maxPrice Maximum ratio of input to output tokens + * @return (tokenAmountOut, spotPriceAfter) + */ + function swapExactAmountIn( + address tokenIn, + uint256 tokenAmountIn, + address tokenOut, + uint256 minAmountOut, + uint256 maxPrice + ) + external + override + _lock_ + _public_ + returns (uint256/* tokenAmountOut */, uint256/* spotPriceAfter */) + { + (Record memory inRecord, uint256 realInBalance) = _getInputToken(tokenIn); + Record memory outRecord = _getOutputToken(tokenOut); + + require( + tokenAmountIn <= bmul(inRecord.balance, MAX_IN_RATIO), + "ERR_MAX_IN_RATIO" + ); + + uint256 spotPriceBefore = calcSpotPrice( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + _swapFee + ); + require(spotPriceBefore <= maxPrice, "ERR_BAD_LIMIT_PRICE"); + + uint256 tokenAmountOut = calcOutGivenIn( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + tokenAmountIn, + _swapFee + ); + + require(tokenAmountOut >= minAmountOut, "ERR_LIMIT_OUT"); + + _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); + _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); + + // Update the in-memory record for the spotPriceAfter calculation, + // then update the storage record with the local balance. + outRecord.balance = bsub(outRecord.balance, tokenAmountOut); + _records[tokenOut].balance = outRecord.balance; + // If needed, update the output token's weight. + _decreaseDenorm(outRecord, tokenOut); + + realInBalance = badd(realInBalance, tokenAmountIn); + _updateInputToken(tokenIn, inRecord, realInBalance); + if (inRecord.ready) { + inRecord.balance = realInBalance; + } + + uint256 spotPriceAfter = calcSpotPrice( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + _swapFee + ); + + require(spotPriceAfter >= spotPriceBefore, "ERR_MATH_APPROX_2"); + require(spotPriceAfter <= maxPrice, "ERR_LIMIT_PRICE"); + require( + spotPriceBefore <= bdiv(tokenAmountIn, tokenAmountOut), + "ERR_MATH_APPROX" + ); + + emit LOG_SWAP(msg.sender, tokenIn, tokenOut, tokenAmountIn, tokenAmountOut); + + return (tokenAmountOut, spotPriceAfter); + } + + /** + * @dev Trades at most `maxAmountIn` of `tokenIn` for exactly `tokenAmountOut` + * of `tokenOut`. + * + * Returns the actual input amount and the new spot price after the swap, + * which can not exceed `maxPrice`. + * + * @param tokenIn Token to swap in + * @param maxAmountIn Maximum amount of `tokenIn` to pay + * @param tokenOut Token to swap out + * @param tokenAmountOut Exact amount of `tokenOut` to receive + * @param maxPrice Maximum ratio of input to output tokens + * @return (tokenAmountIn, spotPriceAfter) + */ + function swapExactAmountOut( + address tokenIn, + uint256 maxAmountIn, + address tokenOut, + uint256 tokenAmountOut, + uint256 maxPrice + ) + external + override + _lock_ + _public_ + returns (uint256 /* tokenAmountIn */, uint256 /* spotPriceAfter */) + { + (Record memory inRecord, uint256 realInBalance) = _getInputToken(tokenIn); + Record memory outRecord = _getOutputToken(tokenOut); + + require( + tokenAmountOut <= bmul(outRecord.balance, MAX_OUT_RATIO), + "ERR_MAX_OUT_RATIO" + ); + + uint256 spotPriceBefore = calcSpotPrice( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + _swapFee + ); + require(spotPriceBefore <= maxPrice, "ERR_BAD_LIMIT_PRICE"); + + uint256 tokenAmountIn = calcInGivenOut( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + tokenAmountOut, + _swapFee + ); + + require(tokenAmountIn <= maxAmountIn, "ERR_LIMIT_IN"); + + _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); + _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); + + // Update the in-memory record for the spotPriceAfter calculation, + // then update the storage record with the local balance. + outRecord.balance = bsub(outRecord.balance, tokenAmountOut); + _records[tokenOut].balance = outRecord.balance; + // If needed, update the output token's weight. + _decreaseDenorm(outRecord, tokenOut); + + // Update the balance and (if necessary) weight of the input token. + realInBalance = badd(realInBalance, tokenAmountIn); + _updateInputToken(tokenIn, inRecord, realInBalance); + if (inRecord.ready) { + inRecord.balance = realInBalance; + } + + uint256 spotPriceAfter = calcSpotPrice( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + _swapFee + ); + + require(spotPriceAfter >= spotPriceBefore, "ERR_MATH_APPROX"); + require(spotPriceAfter <= maxPrice, "ERR_LIMIT_PRICE"); + require( + spotPriceBefore <= bdiv(tokenAmountIn, tokenAmountOut), + "ERR_MATH_APPROX" + ); + + emit LOG_SWAP(msg.sender, tokenIn, tokenOut, tokenAmountIn, tokenAmountOut); + + return (tokenAmountIn, spotPriceAfter); + } + +/* ========== Config Queries ========== */ + /** + * @dev Check if swapping tokens and joining the pool is allowed. + */ + function isPublicSwap() external view override returns (bool) { + return _publicSwap; + } + + function getSwapFee() external view override _viewlock_ returns (uint256/* swapFee */) { + return _swapFee; + } + + function getExitFee() external view override _viewlock_ returns (uint256/* exitFee */) { + return EXIT_FEE; + } + + /** + * @dev Returns the controller address. + */ + function getController() external view override returns (address) + { + return _controller; + } + + /** + * @dev Returns the exit fee recipient address. + */ + function getExitFeeRecipient() external view override returns (address) { + return _exitFeeRecipient; + } + +/* ========== Token Queries ========== */ + + /** + * @dev Check if a token is bound to the pool. + */ + function isBound(address t) external view override returns (bool) { + return _records[t].bound; + } + + /** + * @dev Get the number of tokens bound to the pool. + */ + function getNumTokens() external view override returns (uint256) { + return _tokens.length; + } + + /** + * @dev Get all bound tokens. + */ + function getCurrentTokens() + external + view + override + _viewlock_ + returns (address[] memory tokens) + { + tokens = _tokens; + } + + /** + * @dev Returns the list of tokens which have a desired weight above 0. + * Tokens with a desired weight of 0 are set to be phased out of the pool. + */ + function getCurrentDesiredTokens() + external + view + override + _viewlock_ + returns (address[] memory tokens) + { + address[] memory tempTokens = _tokens; + tokens = new address[](tempTokens.length); + uint256 usedIndex = 0; + for (uint256 i = 0; i < tokens.length; i++) { + address token = tempTokens[i]; + if (_records[token].desiredDenorm > 0) { + tokens[usedIndex++] = token; + } + } + assembly { mstore(tokens, usedIndex) } + } + + /** + * @dev Returns the denormalized weight of a bound token. + */ + function getDenormalizedWeight(address token) + external + view + override + _viewlock_ + returns (uint256/* denorm */) + { + require(_records[token].bound, "ERR_NOT_BOUND"); + return _records[token].denorm; + } + + /** + * @dev Returns the record for a token bound to the pool. + */ + function getTokenRecord(address token) + external + view + override + _viewlock_ + returns (Record memory record) + { + record = _records[token]; + require(record.bound, "ERR_NOT_BOUND"); + } + + /** + * @dev Finds the first token which is both initialized and has a + * desired weight above 0, then returns the address of that token + * and the extrapolated value of the pool in terms of that token. + * + * The value is extrapolated by multiplying the token's + * balance by the reciprocal of its normalized weight. + * @return (token, extrapolatedValue) + */ + function extrapolatePoolValueFromToken() + external + view + override + _viewlock_ + returns (address/* token */, uint256/* extrapolatedValue */) + { + address token; + uint256 extrapolatedValue; + uint256 len = _tokens.length; + for (uint256 i = 0; i < len; i++) { + token = _tokens[i]; + Record storage record = _records[token]; + if (record.ready && record.desiredDenorm > 0) { + extrapolatedValue = bmul( + record.balance, + bdiv(_totalWeight, record.denorm) + ); + break; + } + } + require(extrapolatedValue > 0, "ERR_NONE_READY"); + return (token, extrapolatedValue); + } + + /** + * @dev Get the total denormalized weight of the pool. + */ + function getTotalDenormalizedWeight() + external + view + override + _viewlock_ + returns (uint256) + { + return _totalWeight; + } + + /** + * @dev Returns the stored balance of a bound token. + */ + function getBalance(address token) external view override _viewlock_ returns (uint256) { + Record storage record = _records[token]; + require(record.bound, "ERR_NOT_BOUND"); + return record.balance; + } + + /** + * @dev Get the minimum balance of an uninitialized token. + * Note: Throws if the token is initialized. + */ + function getMinimumBalance(address token) external view override _viewlock_ returns (uint256) { + Record memory record = _records[token]; + require(record.bound, "ERR_NOT_BOUND"); + require(!record.ready, "ERR_READY"); + return _minimumBalances[token]; + } + + /** + * @dev Returns the balance of a token which is used in price + * calculations. If the token is initialized, this is the + * stored balance; if not, this is the minimum balance. + */ + function getUsedBalance(address token) external view override _viewlock_ returns (uint256) { + Record memory record = _records[token]; + require(record.bound, "ERR_NOT_BOUND"); + if (!record.ready) { + return _minimumBalances[token]; + } + return record.balance; + } + +/* ========== Price Queries ========== */ + /** + * @dev Returns the spot price for `tokenOut` in terms of `tokenIn`. + */ + function getSpotPrice(address tokenIn, address tokenOut) + external + view + override + _viewlock_ + returns (uint256) + { + (Record memory inRecord,) = _getInputToken(tokenIn); + Record memory outRecord = _getOutputToken(tokenOut); + return + calcSpotPrice( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + _swapFee + ); + } + +/* ========== Pool Share Internal Functions ========== */ + + function _pullPoolShare(address from, uint256 amount) internal { + _pull(from, amount); + } + + function _pushPoolShare(address to, uint256 amount) internal { + _push(to, amount); + } + + function _mintPoolShare(uint256 amount) internal { + _mint(amount); + } + + function _burnPoolShare(uint256 amount) internal { + _burn(amount); + } + +/* ========== Underlying Token Internal Functions ========== */ + // 'Underlying' token-manipulation functions make external calls but are NOT locked + // You must `_lock_` or otherwise ensure reentry-safety + + function _pullUnderlying( + address erc20, + address from, + uint256 amount + ) internal { + (bool success, bytes memory data) = erc20.call( + abi.encodeWithSelector( + IERC20.transferFrom.selector, + from, + address(this), + amount + ) + ); + require( + success && (data.length == 0 || abi.decode(data, (bool))), + "ERR_ERC20_FALSE" + ); + } + + function _pushUnderlying( + address erc20, + address to, + uint256 amount + ) internal { + (bool success, bytes memory data) = erc20.call( + abi.encodeWithSelector( + IERC20.transfer.selector, + to, + amount + ) + ); + require( + success && (data.length == 0 || abi.decode(data, (bool))), + "ERR_ERC20_FALSE" + ); + } + +/* ========== Token Management Internal Functions ========== */ + + /** + * @dev Bind a token by address without actually depositing a balance. + * The token will be unable to be swapped out until it reaches the minimum balance. + * Note: Token must not already be bound. + * Note: `minimumBalance` should represent an amount of the token which is worth + * the portion of the current pool value represented by the minimum weight. + * @param token Address of the token to bind + * @param minimumBalance minimum balance to reach before the token can be swapped out + * @param desiredDenorm Desired weight for the token. + */ + function _bind( + address token, + uint256 minimumBalance, + uint96 desiredDenorm + ) internal { + require(!_records[token].bound, "ERR_IS_BOUND"); + + require(desiredDenorm >= MIN_WEIGHT, "ERR_MIN_WEIGHT"); + require(desiredDenorm <= MAX_WEIGHT, "ERR_MAX_WEIGHT"); + require(minimumBalance >= MIN_BALANCE, "ERR_MIN_BALANCE"); + + _records[token] = Record({ + bound: true, + ready: false, + lastDenormUpdate: 0, + denorm: 0, + desiredDenorm: desiredDenorm, + index: uint8(_tokens.length), + balance: 0 + }); + _tokens.push(token); + _minimumBalances[token] = minimumBalance; + emit LOG_TOKEN_ADDED(token, desiredDenorm, minimumBalance); + } + + /** + * @dev Remove a token from the pool. + * Replaces the address in the tokens array with the last address, + * then removes it from the array. + * Note: This should only be called after the total weight has been adjusted. + * Note: Must be called in a function with: + * - _lock_ modifier to prevent reentrance + * - requirement that the token is bound + */ + function _unbind(address token) internal { + Record memory record = _records[token]; + uint256 tokenBalance = record.balance; + + // Swap the token-to-unbind with the last token, + // then delete the last token + uint256 index = record.index; + uint256 last = _tokens.length - 1; + // Only swap the token with the last token if it is not + // already at the end of the array. + if (index != last) { + _tokens[index] = _tokens[last]; + _records[_tokens[index]].index = uint8(index); + } + _tokens.pop(); + _records[token] = Record({ + bound: false, + ready: false, + lastDenormUpdate: 0, + denorm: 0, + desiredDenorm: 0, + index: 0, + balance: 0 + }); + // transfer any remaining tokens out + _pushUnderlying(token, address(_unbindHandler), tokenBalance); + _unbindHandler.handleUnbindToken(token, tokenBalance); + emit LOG_TOKEN_REMOVED(token); + } + + function _setDesiredDenorm(address token, uint96 desiredDenorm) internal { + Record storage record = _records[token]; + require(record.bound, "ERR_NOT_BOUND"); + // If the desired weight is 0, this will trigger a gradual unbinding of the token. + // Therefore the weight only needs to be above the minimum weight if it isn't 0. + require( + desiredDenorm >= MIN_WEIGHT || desiredDenorm == 0, + "ERR_MIN_WEIGHT" + ); + require(desiredDenorm <= MAX_WEIGHT, "ERR_MAX_WEIGHT"); + record.desiredDenorm = desiredDenorm; + emit LOG_DESIRED_DENORM_SET(token, desiredDenorm); + } + + function _increaseDenorm(Record memory record, address token) internal { + // If the weight does not need to increase or the token is not + // initialized, don't do anything. + if ( + record.denorm >= record.desiredDenorm || + !record.ready || + now - record.lastDenormUpdate < WEIGHT_UPDATE_DELAY + ) return; + uint96 oldWeight = record.denorm; + uint96 denorm = record.desiredDenorm; + uint256 maxDiff = bmul(oldWeight, WEIGHT_CHANGE_PCT); + uint256 diff = bsub(denorm, oldWeight); + if (diff > maxDiff) { + denorm = uint96(badd(oldWeight, maxDiff)); + diff = maxDiff; + } + // If new total weight exceeds the maximum, do not update + uint256 newTotalWeight = badd(_totalWeight, diff); + if (newTotalWeight > MAX_TOTAL_WEIGHT) return; + _totalWeight = newTotalWeight; + // Update the in-memory denorm value for spot-price computations. + record.denorm = denorm; + // Update the storage record + _records[token].denorm = denorm; + _records[token].lastDenormUpdate = uint40(now); + emit LOG_DENORM_UPDATED(token, denorm); + } + + function _decreaseDenorm(Record memory record, address token) internal { + // If the weight does not need to decrease, don't do anything. + if ( + record.denorm <= record.desiredDenorm || + !record.ready || + now - record.lastDenormUpdate < WEIGHT_UPDATE_DELAY + ) return; + uint96 oldWeight = record.denorm; + uint96 denorm = record.desiredDenorm; + uint256 maxDiff = bmul(oldWeight, WEIGHT_CHANGE_PCT); + uint256 diff = bsub(oldWeight, denorm); + if (diff > maxDiff) { + denorm = uint96(bsub(oldWeight, maxDiff)); + diff = maxDiff; + } + if (denorm <= MIN_WEIGHT) { + denorm = 0; + _totalWeight = bsub(_totalWeight, denorm); + // Because this is removing the token from the pool, the + // in-memory denorm value is irrelevant, as it is only used + // to calculate the new spot price, but the spot price calc + // will throw if it is passed 0 for the denorm. + _unbind(token); + } else { + _totalWeight = bsub(_totalWeight, diff); + // Update the in-memory denorm value for spot-price computations. + record.denorm = denorm; + // Update the stored denorm value + _records[token].denorm = denorm; + _records[token].lastDenormUpdate = uint40(now); + emit LOG_DENORM_UPDATED(token, denorm); + } + } + + /** + * @dev Handles weight changes and initialization of an + * input token. + * + * If the token is not initialized and the new balance is + * still below the minimum, this will not do anything. + * + * If the token is not initialized but the new balance will + * bring the token above the minimum balance, this will + * mark the token as initialized, remove the minimum + * balance and set the weight to the minimum weight plus + * 1%. + * + * + * @param token Address of the input token + * @param record Token record with minimums applied to the balance + * and weight if the token was uninitialized. + */ + function _updateInputToken( + address token, + Record memory record, + uint256 realBalance + ) + internal + { + if (!record.ready) { + // Check if the minimum balance has been reached + if (realBalance >= record.balance) { + // Remove the minimum balance record + _minimumBalances[token] = 0; + // Mark the token as initialized + _records[token].ready = true; + record.ready = true; + emit LOG_TOKEN_READY(token); + // Set the initial denorm value to the minimum weight times one plus + // the ratio of the increase in balance over the minimum to the minimum + // balance. + // weight = (1 + ((bal - min_bal) / min_bal)) * min_weight + uint256 additionalBalance = bsub(realBalance, record.balance); + uint256 balRatio = bdiv(additionalBalance, record.balance); + record.denorm = uint96(badd(MIN_WEIGHT, bmul(MIN_WEIGHT, balRatio))); + _records[token].denorm = record.denorm; + _records[token].lastDenormUpdate = uint40(now); + _totalWeight = badd(_totalWeight, record.denorm); + emit LOG_DENORM_UPDATED(token, record.denorm); + } else { + uint256 realToMinRatio = bdiv( + bsub(record.balance, realBalance), + record.balance + ); + uint256 weightPremium = bmul(MIN_WEIGHT / 10, realToMinRatio); + record.denorm = uint96(badd(MIN_WEIGHT, weightPremium)); + } + // If the token is still not ready, do not adjust the weight. + } else { + // If the token is already initialized, update the weight (if any adjustment + // is needed). + _increaseDenorm(record, token); + } + // Regardless of whether the token is initialized, store the actual new balance. + _records[token].balance = realBalance; + } + +/* ========== Token Query Internal Functions ========== */ + + /** + * @dev Get the record for a token which is being swapped in. + * The token must be bound to the pool. If the token is not + * initialized (meaning it does not have the minimum balance) + * this function will return the actual balance of the token + * which the pool holds, but set the record's balance and weight + * to the token's minimum balance and the pool's minimum weight. + * This allows the token swap to be priced correctly even if the + * pool does not own any of the tokens. + */ + function _getInputToken(address token) + internal + view + returns (Record memory record, uint256 realBalance) + { + record = _records[token]; + require(record.bound, "ERR_NOT_BOUND"); + + realBalance = record.balance; + // If the input token is not initialized, we use the minimum + // initial weight and minimum initial balance instead of the + // real values for price and output calculations. + if (!record.ready) { + record.balance = _minimumBalances[token]; + uint256 realToMinRatio = bdiv( + bsub(record.balance, realBalance), + record.balance + ); + uint256 weightPremium = bmul(MIN_WEIGHT / 10, realToMinRatio); + record.denorm = uint96(badd(MIN_WEIGHT, weightPremium)); + } + } + + function _getOutputToken(address token) + internal + view + returns (Record memory record) + { + record = _records[token]; + require(record.bound, "ERR_NOT_BOUND"); + // Tokens which have not reached their minimum balance can not be + // swapped out. + require(record.ready, "ERR_OUT_NOT_READY"); + } +} + + +interface TokenUnbindHandler { + /** + * @dev Receive `amount` of `token` from the pool. + */ + function handleUnbindToken(address token, uint256 amount) external; +} \ 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 0000000000..3b091f62fb --- /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/SafeMath.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/SafeMath.sol new file mode 100644 index 0000000000..b644454dd5 --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/SafeMath.sol @@ -0,0 +1,159 @@ +// 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; + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/Safemoon.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/Safemoon.sol new file mode 100644 index 0000000000..74cdc8fddd --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/Safemoon.sol @@ -0,0 +1,1166 @@ +/** + *Submitted for verification at BscScan.com on 2021-03-01 +*/ + +/** + *Submitted for verification at BscScan.com on 2021-03-01 +*/ + +/** + + #BEE + + #LIQ+#RFI+#SHIB+#DOGE = #BEE + + #SAFEMOON features: + 3% fee auto add to the liquidity pool to locked forever when selling + 2% fee auto distribute to all holders + I created a black hole so #Bee token will deflate itself in supply with every transaction + 50% Supply is burned at start. + + + */ + +pragma solidity ^0.6.11; +// SPDX-License-Identifier: Unlicensed +interface IERC20 { + + 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; + } +} + +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; + } +} + + +/** + * @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) { + // 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); + } + + /** + * @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); + } + } + } +} + +/** + * @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; + address private _previousOwner; + uint256 private _lockTime; + + 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 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; + } + + function geUnlockTime() public view returns (uint256) { + return _lockTime; + } + + //Locks the contract for owner for the amount of time provided + function lock(uint256 time) public virtual onlyOwner { + _previousOwner = _owner; + _owner = address(0); + _lockTime = now + time; + emit OwnershipTransferred(_owner, address(0)); + } + + //Unlocks the contract for owner when _lockTime is exceeds + function unlock() public virtual { + require(_previousOwner == msg.sender, "You don't have permission to unlock"); + require(now > _lockTime , "Contract is locked until 7 days"); + emit OwnershipTransferred(_owner, _previousOwner); + _owner = _previousOwner; + } +} + +// pragma solidity >=0.5.0; + +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; +} + + +// pragma solidity >=0.5.0; + +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; +} + +// 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 SafeMoon is Context, IERC20, Ownable { + using SafeMath for uint256; + using Address for address; + + 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; + + uint256 private constant MAX = ~uint256(0); + uint256 private _tTotal = 1000000000 * 10**6 * 10**9; + uint256 private _rTotal = (MAX - (MAX % _tTotal)); + uint256 private _tFeeTotal; + + string private _name = "SafeMoon"; + string private _symbol = "SAFEMOON"; + uint8 private _decimals = 9; + + uint256 public _taxFee = 5; + uint256 private _previousTaxFee = _taxFee; + + uint256 public _liquidityFee = 5; + uint256 private _previousLiquidityFee = _liquidityFee; + + IUniswapV2Router02 public immutable uniswapV2Router; + address public immutable uniswapV2Pair; + + bool inSwapAndLiquify; + bool public swapAndLiquifyEnabled = true; + + uint256 public _maxTxAmount = 5000000 * 10**6 * 10**9; + uint256 private numTokensSellToAddToLiquidity = 500000 * 10**6 * 10**9; + + event MinTokensBeforeSwapUpdated(uint256 minTokensBeforeSwap); + 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(0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F); + // Create a uniswap pair for this new token + uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()) + .createPair(address(this), _uniswapV2Router.WETH()); + + // set the rest of the contract variables + uniswapV2Router = _uniswapV2Router; + + //exclude owner and this contract from fee + _isExcludedFromFee[owner()] = true; + _isExcludedFromFee[address(this)] = true; + + emit Transfer(address(0), _msgSender(), _tTotal); + } + + 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; + } + + function totalSupply() public 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) public override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + function allowance(address owner, address spender) public view override returns (uint256) { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) public override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + function transferFrom(address sender, address recipient, uint256 amount) public 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 isExcludedFromReward(address account) public view returns (bool) { + return _isExcluded[account]; + } + + function totalFees() public view returns (uint256) { + return _tFeeTotal; + } + + function deliver(uint256 tAmount) public { + 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) public 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) public 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 _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 excludeFromFee(address account) public onlyOwner { + _isExcludedFromFee[account] = true; + } + + function includeInFee(address account) public onlyOwner { + _isExcludedFromFee[account] = false; + } + + function setTaxFeePercent(uint256 taxFee) external onlyOwner() { + _taxFee = taxFee; + } + + function setLiquidityFeePercent(uint256 liquidityFee) external onlyOwner() { + _liquidityFee = liquidityFee; + } + + function setMaxTxPercent(uint256 maxTxPercent) external onlyOwner() { + _maxTxAmount = _tTotal.mul(maxTxPercent).div( + 10**2 + ); + } + + function setSwapAndLiquifyEnabled(bool _enabled) public onlyOwner { + swapAndLiquifyEnabled = _enabled; + emit SwapAndLiquifyEnabledUpdated(_enabled); + } + + //to recieve ETH from uniswapV2Router when swaping + receive() external payable {} + + 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) public view returns(bool) { + return _isExcludedFromFee[account]; + } + + 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."); + + // is the token balance of this contract address over the min number of + // tokens that we need to initiate a swap + liquidity lock? + // also, don't get caught in a circular liquidity event. + // also, don't swap & liquify if sender is uniswap pair. + uint256 contractTokenBalance = balanceOf(address(this)); + + if(contractTokenBalance >= _maxTxAmount) + { + contractTokenBalance = _maxTxAmount; + } + + bool overMinTokenBalance = contractTokenBalance >= numTokensSellToAddToLiquidity; + if ( + overMinTokenBalance && + !inSwapAndLiquify && + from != uniswapV2Pair && + swapAndLiquifyEnabled + ) { + contractTokenBalance = numTokensSellToAddToLiquidity; + //add liquidity + swapAndLiquify(contractTokenBalance); + } + + //indicates if fee should be deducted from transfer + bool takeFee = true; + + //if any account belongs to _isExcludedFromFee account then remove the fee + if(_isExcludedFromFee[from] || _isExcludedFromFee[to]){ + takeFee = false; + } + + //transfer amount, it will take tax, burn, liquidity fee + _tokenTransfer(from,to,amount,takeFee); + } + + function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap { + // split the contract balance into halves + uint256 half = contractTokenBalance.div(2); + uint256 otherHalf = contractTokenBalance.sub(half); + + // capture the contract's current ETH balance. + // this is so that we can capture exactly the amount of ETH that the + // swap creates, and not make the liquidity event include any ETH that + // has been manually sent to the contract + uint256 initialBalance = address(this).balance; + + // swap tokens for ETH + swapTokensForEth(half); // <- this breaks the ETH -> HATE swap when swap+liquify is triggered + + // how much ETH did we just swap into? + uint256 newBalance = address(this).balance.sub(initialBalance); + + // add liquidity to uniswap + addLiquidity(otherHalf, newBalance); + + emit SwapAndLiquify(half, newBalance, otherHalf); + } + + 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), + block.timestamp + ); + } + + function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private { + // approve token transfer to cover all possible scenarios + _approve(address(this), address(uniswapV2Router), tokenAmount); + + // add the liquidity + uniswapV2Router.addLiquidityETH{value: ethAmount}( + address(this), + tokenAmount, + 0, // slippage is unavoidable + 0, // slippage is unavoidable + owner(), + block.timestamp + ); + } + + //this method is responsible for taking all fee, if takeFee is true + function _tokenTransfer(address sender, address recipient, uint256 amount,bool takeFee) private { + 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); + } + + + + +} diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/SpaceGoat.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/SpaceGoat.sol new file mode 100644 index 0000000000..b204542e2a --- /dev/null +++ b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/SpaceGoat.sol @@ -0,0 +1,1198 @@ +/** + *Submitted for verification at BscScan.com on 2021-05-21 +*/ + +pragma solidity ^0.6.11; +// SPDX-License-Identifier: Unlicensed +interface IERC20 { + + 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; + } +} + +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; + } +} + + +/** + * @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) { + // 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); + } + + /** + * @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); + } + } + } +} + +/** + * @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; + address private _previousOwner; + + 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 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; + } +} + + +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 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 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; +} + +contract SpaceGoat is Context, IERC20, Ownable { + using SafeMath for uint256; + using Address for address; + address contractOwner; + + 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; + + uint256 private MAX = ~uint256(0); + uint256 private constant _tTotal = 1000000000 * 10**6 * 10**9; + uint256 private _rTotal = (MAX - (MAX % _tTotal)); + uint256 private _tFeeTotal; + + string private constant _name = "SPACEGOAT TOKEN"; + string private constant _symbol = "SGT"; + uint8 private constant _decimals = 9; + + uint256 public _taxFee = 4; //4% holder distribution + uint256 private _previousTaxFee = _taxFee; + + uint256 public _liquidityFee = 2; //2% liquidity pool + uint256 private _previousLiquidityFee = _liquidityFee; + + uint256 public _charityPlusExmarketFee = 4; //1% charity fee + 1% exMarket fee + 2% burn wallet + uint256 private _previouscharityPlusExmarketFee = _charityPlusExmarketFee; + + address payable private _charityWallet = 0xD42e4F076721b92f1B87E4A0B3d0bAa354b2a7d8; // Charity Fund + address payable private _exMarketWallet = 0x8fc0b3DC7690a3CD96a0033B48073ACa7c859713; // Exchange + Marketing Fund + address payable private _burnWallet = 0x0000000000000000000000000000000000000001; + + IUniswapV2Router02 public uniswapV2Router; + address public uniswapV2Pair; + address private pancake = 0x10ED43C718714eb63d5aA57B78B54704E256024E; + + bool inSwapAndLiquify; + bool public swapAndLiquifyEnabled = false; + bool public burnEnabled = true; + + uint256 public _maxTxAmount = 5000000 * 10**6 * 10**9; + uint256 private constant numTokensSellToAddToLiquidity = 500000 * 10**6 * 10**9; + + event MinTokensBeforeSwapUpdated(uint256 minTokensBeforeSwap); + event SwapAndLiquifyEnabledUpdated(bool enabled); + event BurnEnabledUpdated(bool enabled); + event SwapAndLiquify( + uint256 tokensSwapped, + uint256 ethReceived, + uint256 tokensIntoLiquidity + ); + + modifier lockTheSwap { + inSwapAndLiquify = true; + _; + inSwapAndLiquify = false; + } + + constructor () public { + _rOwned[_msgSender()] = _rTotal; + + setRouterAddress(pancake); + //exclude owner and this contract from fee + _isExcludedFromFee[owner()] = true; + _isExcludedFromFee[address(this)] = true; + + contractOwner = msg.sender; + + emit Transfer(address(0), _msgSender(), _tTotal); + } + + function name() public pure returns (string memory) { + return _name; + } + + function symbol() public pure returns (string memory) { + return _symbol; + } + + + function decimals() public pure returns (uint8) { + return _decimals; + } + + function totalSupply() public 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) public override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + function allowance(address owner, address spender) public view override returns (uint256) { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) public override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + function transferFrom(address sender, address recipient, uint256 amount) public 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 isExcludedFromReward(address account) public view returns (bool) { + return _isExcluded[account]; + } + + function totalFees() public view returns (uint256) { + return _tFeeTotal; + } + + function deliver(uint256 tAmount) public onlyOwner{ + 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) public 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) public onlyOwner() { + require(!_isExcluded[account], "Account is not 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 not 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 _transferBothExcluded(address sender, address recipient, uint256 tAmount) private { + (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity, uint tcharityPlusExmarketFee) = _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); + _takecharityPlusExmarketFee(tcharityPlusExmarketFee); + emit Transfer(sender, recipient, tTransferAmount); + } + + function excludeFromFee(address account) public onlyOwner { + _isExcludedFromFee[account] = true; + } + + function includeInFee(address account) public onlyOwner { + _isExcludedFromFee[account] = false; + } + + function setTaxFeePercent(uint256 taxFee) external onlyOwner() { + _taxFee = taxFee; + } + + function setLiquidityFeePercent(uint256 liquidityFee) external onlyOwner() { + _liquidityFee = liquidityFee; + } + + function setRouterAddress(address newRouter) public onlyOwner() { + //Thank you FreezyEx + // current v2 = 0x10ED43C718714eb63d5aA57B78B54704E256024E + // Address the version change problem PancakeSwap + IUniswapV2Router02 _newPancakeRouter = IUniswapV2Router02(newRouter); + uniswapV2Pair = IUniswapV2Factory(_newPancakeRouter.factory()).createPair(address(this), _newPancakeRouter.WETH()); + uniswapV2Router = _newPancakeRouter; + } + + function setcharityPlusExmarketFee(uint256 charityPlusExmarketFee) external onlyOwner() { + _charityPlusExmarketFee = charityPlusExmarketFee; + } + + function setCharityWallet(address payable charityWalletAddress) external onlyOwner() { + _charityWallet = charityWalletAddress; + } + + function setExMarketWallet(address payable exMarketWalletAddress) external onlyOwner() { + _exMarketWallet = exMarketWalletAddress; + } + + function setBurnEnabled(bool _enabled) public onlyOwner { + burnEnabled = _enabled; + emit BurnEnabledUpdated(_enabled); + } + + function setMaxTxPercent(uint256 maxTxPercent) external onlyOwner() { + _maxTxAmount = _tTotal.mul(maxTxPercent).div( + 10**2 + ); + } + + function setSwapAndLiquifyEnabled(bool _enabled) public onlyOwner { + swapAndLiquifyEnabled = _enabled; + emit SwapAndLiquifyEnabledUpdated(_enabled); + } + + //to receive BNB from uniswapV2Router when swapping + receive() external payable {} + + 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) { + (uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity, uint256 tcharityPlusExmarketFee) = _getTValues(tAmount); + (uint256 rAmount, uint256 rTransferAmount, uint256 rFee) = _getRValues(tAmount, tFee, tLiquidity, tcharityPlusExmarketFee, _getRate()); + return (rAmount, rTransferAmount, rFee, tTransferAmount, tFee, tLiquidity, tcharityPlusExmarketFee); + } + + function _getTValues(uint256 tAmount) private view returns (uint256, uint256, uint256, uint256) { + uint256 tFee = calculateTaxFee(tAmount); + uint256 tLiquidity = calculateLiquidityFee(tAmount); + uint tcharityPlusExmarketFee; + if(msg.sender != contractOwner){ + tcharityPlusExmarketFee = calculateCharityPlusExmarketFee(tAmount); + }else{ + tcharityPlusExmarketFee=0; + } + uint256 tTransferAmount = tAmount.sub(tFee).sub(tLiquidity).sub(tcharityPlusExmarketFee); + return (tTransferAmount, tFee, tLiquidity, tcharityPlusExmarketFee); + } + + function _getRValues(uint256 tAmount, uint256 tFee, uint256 tLiquidity, uint256 tcharityPlusExmarketFee, uint256 currentRate) private view returns (uint256, uint256, uint256) { + uint256 rAmount = tAmount.mul(currentRate); + uint256 rFee = tFee.mul(currentRate); + uint256 rLiquidity = tLiquidity.mul(currentRate); + uint256 rcharityPlusExmarketFee; + + if(msg.sender != contractOwner){ + rcharityPlusExmarketFee = tcharityPlusExmarketFee.mul(currentRate); + }else{ + rcharityPlusExmarketFee=0; + } + + uint256 rTransferAmount = rAmount.sub(rFee).sub(rLiquidity).sub(rcharityPlusExmarketFee); + 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 _takecharityPlusExmarketFee(uint256 tcharityPlusExmarketFee) private{ + + uint256 currentRate = _getRate(); + uint256 rCharityFee = tcharityPlusExmarketFee.mul(currentRate); + uint256 burnWallet; + if (burnEnabled) { + //we want to stop splitting fees + burnWallet = rCharityFee.div(2); + } + + uint256 charity = rCharityFee.sub(burnWallet); + uint256 charityFee = charity.div(2); + uint256 exMarketFee = charity.sub(charityFee); + + + _rOwned[_charityWallet] = _rOwned[_charityWallet].add(charityFee); + _rOwned[_exMarketWallet] = _rOwned[_exMarketWallet].add(exMarketFee); + _rOwned[_burnWallet] = _rOwned[_burnWallet].add(burnWallet); + + if(_isExcluded[address(this)]) + _tOwned[address(this)] = _tOwned[address(this)].add(tcharityPlusExmarketFee); + } + + 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 calculateCharityPlusExmarketFee(uint256 _amount) private view returns (uint256) { + return _amount.mul(_charityPlusExmarketFee).div( + 10**2 + ); + } + + function removeAllFee() private { + if(_taxFee == 0 && _liquidityFee == 0 && _charityPlusExmarketFee == 0) return; + + _previousTaxFee = _taxFee; + _previousLiquidityFee = _liquidityFee; + _previouscharityPlusExmarketFee = _charityPlusExmarketFee; + + _taxFee = 0; + _liquidityFee = 0; + _charityPlusExmarketFee=0; + } + + function restoreAllFee() private { + _taxFee = _previousTaxFee; + _liquidityFee = _previousLiquidityFee; + _charityPlusExmarketFee = _previouscharityPlusExmarketFee; + } + + function isExcludedFromFee(address account) public view returns(bool) { + return _isExcludedFromFee[account]; + } + + 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."); + + // is the token balance of this contract address over the min number of + // tokens that we need to initiate a swap + liquidity lock? + // also, don't get caught in a circular liquidity event. + // also, don't swap & liquify if sender is uniswap pair. + uint256 contractTokenBalance = balanceOf(address(this)); + + if(contractTokenBalance >= _maxTxAmount) + { + contractTokenBalance = _maxTxAmount; + } + + bool overMinTokenBalance = contractTokenBalance >= numTokensSellToAddToLiquidity; + + if ( + overMinTokenBalance && + !inSwapAndLiquify && + from != uniswapV2Pair && + swapAndLiquifyEnabled + ) { + contractTokenBalance = numTokensSellToAddToLiquidity; + //add liquidity + swapAndLiquify(contractTokenBalance); + } + + //indicates if fee should be deducted from transfer + bool takeFee = true; + + //if any account belongs to _isExcludedFromFee account then remove the fee + if(_isExcludedFromFee[from] || _isExcludedFromFee[to]){ + takeFee = false; + } + + //transfer amount, it will take tax, burn, liquidity fee + _tokenTransfer(from,to,amount,takeFee); + + } + + function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap { + // split the contract balance into halves + uint256 half = contractTokenBalance.div(2); + uint256 otherHalf = contractTokenBalance.sub(half); + + uint256 initialBalance = address(this).balance; + + // swap tokens for BNB + swapTokensForBNB(half); + + // how much BNB did we just swap into? + uint256 newBalance = address(this).balance.sub(initialBalance); + + // add liquidity to pancakeswap + addLiquidity(otherHalf, newBalance); + + emit SwapAndLiquify(half, newBalance, otherHalf); + } + + + function swapTokensForBNB(uint256 tokenAmount) private { + // generate the pancakeswap pair path of token -> BNB + 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 BNB + path, + address(this), + block.timestamp + ); + } + + function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private returns(uint){ + // approve token transfer to cover all possible scenarios + _approve(address(this), address(uniswapV2Router), tokenAmount); + + // add the liquidity + uniswapV2Router.addLiquidityETH{value: ethAmount}( + address(this), + tokenAmount, + 0, // slippage is unavoidable + 0, // slippage is unavoidable + owner(), + block.timestamp + ); + return tokenAmount; + } + + //this method is responsible for taking all fee, if takeFee is true + function _tokenTransfer(address sender, address recipient, uint256 amount,bool takeFee) private { + 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]) { + _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, uint256 tcharityPlusExmarketFee) = _getValues(tAmount); + _rOwned[sender] = _rOwned[sender].sub(rAmount); + _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount); + _takeLiquidity(tLiquidity); + _reflectFee(rFee, tFee); + _takecharityPlusExmarketFee(tcharityPlusExmarketFee); + + 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,uint256 tcharityPlusExmarketFee) = _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); + _takecharityPlusExmarketFee(tcharityPlusExmarketFee); + + 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,uint256 tcharityPlusExmarketFee) = _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); + _takecharityPlusExmarketFee(tcharityPlusExmarketFee); + + emit Transfer(sender, recipient, tTransferAmount); + } + +} \ No newline at end of file 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 0000000000..a302c1b986 --- /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 0000000000..3e9f2e9dd8 --- /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 0000000000..83d5de8744 --- /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 0000000000..9d0f9d8ad6 --- /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 0000000000..bf84cf99b1 --- /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 0000000000..22d5f2a6e4 --- /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 0000000000..f91d42b357 --- /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 0000000000..2860f95b68 --- /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 0000000000..0fe754f751 --- /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 0000000000..750b335c6e --- /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 0000000000..6f862e4516 --- /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 From f1cab576edeeb028bde4e550e8356735dd4d16d1 Mon Sep 17 00:00:00 2001 From: xueyue Date: Fri, 29 Mar 2024 14:00:56 +0800 Subject: [PATCH 2/5] remove chinese description --- slither/detectors/defi/defi_action_nested.py | 227 +++++++++--------- slither/detectors/defi/k_value_error.py | 22 +- slither/detectors/functions/modifier_utils.py | 20 +- 3 files changed, 123 insertions(+), 146 deletions(-) diff --git a/slither/detectors/defi/defi_action_nested.py b/slither/detectors/defi/defi_action_nested.py index 9fc8e97818..c7d2cf5a5b 100644 --- a/slither/detectors/defi/defi_action_nested.py +++ b/slither/detectors/defi/defi_action_nested.py @@ -174,167 +174,162 @@ def _check_call_if_transfer_from_user(self,call,func): (isinstance(call.arguments[0],Identifier) and call.arguments[0].value in func.parameters): return True,call.arguments - # 检查一个call是否是erc20向用户转账的call,返回是/否以及call本身 - 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"]: - # 第一个转账参数是msg.sender或外部传入的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 - - # 检查一个node中是否有mapping操作,是否是一个对msg.sender或者input参数进行赋值的,返回是/否,以及对应所具体赋值的变量 - def _check_node_if_mapping_assignment_with_sender_or_param(self,node,func): - if isinstance(node.expression,AssignmentOperation): + 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) + 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) - # 质押或存入的特点:有向内转账,有使用msgsender或者是传入参数的用户数组操作,有数据依赖 - 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"): + 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: - # 检查是否存在向用户转账的ERC20转账函数 - transfer_ret=self._check_call_if_transfer_from_user(call,func) + # 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 - # 是否有数组操作,并且参数是msg.sender或者func参数中的address类型参数 - assin_ret=self._check_node_if_mapping_assignment_with_sender_or_param(node,func) + 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 - # 数组操作和转账之间存在数据依赖 - if self._check_two_arguments_if_dependent(call_arguments,var_read,func): - dependencyScore=self.defaultDependencyScore + 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 + return diffscore, transferMatchScore, assignementToUserScore, dependencyScore - # 提取和取消质押的特点:有向外转账,有使用msgsender或者是传入参数的用户数组操作,有数据依赖 - 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"): + 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: - # 检查是否存在向用户转账的ERC20转账函数 - transfer_ret=self._check_call_if_transfer_to_user(call,func) + # 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 - # 是否有数组操作,并且参数是msg.sender或者func参数中的address类型参数 - assin_ret=self._check_node_if_mapping_assignment_with_sender_or_param(node,func) + 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 - # 数组操作和转账之间存在数据依赖 - if self._check_two_arguments_if_dependent(call_arguments,var_read,func): - dependencyScore=self.defaultDependencyScore - return diffscore,transferMatchScore,assignementToUserScore,dependencyScore - - # lending特点,有向外转账,有基于用户的mapping操作 - 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"): + 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: - # 检查是否存在向用户转账的ERC20转账函数 - transfer_ret=self._check_call_if_transfer_to_user(call,func) + # 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 - # 是否有数组操作,并且参数是msg.sender或者func参数中的address类型参数 - assin_ret=self._check_node_if_mapping_assignment_with_sender_or_param(node,func) + 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 - # 数组操作和转账之间存在数据依赖 - if self._check_two_arguments_if_dependent(call_arguments,var_read,func): - dependencyScore=self.defaultDependencyScore - return diffscore,transferMatchScore,assignementToUserScore,dependencyScore + 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 - # liquidate的特点,有向外转账,有向内转账,有mapping操作 - def _check_function_if_liquidate(self,func:FunctionContract): - liquidateDiffScore=difflib.SequenceMatcher(a="liquidate", b=func.name.lower()).ratio() + 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"): + 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: - # 检查是否存在向用户转账的ERC20转账函数 - transfer_to_ret=self._check_call_if_transfer_to_user(call,func) - transfer_from_ret=self._check_call_if_transfer_from_user(call,func) + # 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 + 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 + transferFromScore = self.liquidateTransferFromScore - # 是否有数组操作,并且参数是msg.sender或者func参数中的address类型参数 - assin_ret=self._check_node_if_mapping_assignment_with_sender_or_param(node,func) + # 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 - # 数组操作和转账之间存在数据依赖 - if self._check_two_arguments_if_dependent(call_arguments,var_read,func): - dependencyScore=self.defaultDependencyScore - return diffscore,transferToScore+transferFromScore,assignementToUserScore,dependencyScore + 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)] + 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)): + 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=[] + detection_result = [] for c in self.contracts: - detection_result=self.checkIfHavePriceManipulation(c) + 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 it's father defi action which have risk of indirectly generating arbitrage space", + " 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) diff --git a/slither/detectors/defi/k_value_error.py b/slither/detectors/defi/k_value_error.py index e0873f7c97..fbf632a7c1 100644 --- a/slither/detectors/defi/k_value_error.py +++ b/slither/detectors/defi/k_value_error.py @@ -21,25 +21,7 @@ class KValueError(AbstractDetector): ) # region wiki_exploit_scenario - WIKI_EXPLOIT_SCENARIO = """ -```solidity -contract A { - uint x; - constructor() public { - x = 0; - } - function A() public { - x = 1; - } - - function test() public returns(uint) { - return x; - } -} -``` -In Solidity [0.4.22](https://github.com/ethereum/solidity/releases/tag/v0.4.23), a contract with both constructor schemes will compile. The first constructor will take precedence over the second, which may be unintended.""" - # endregion wiki_exploit_scenario - + WIKI_EXPLOIT_SCENARIO = """""" WIKI_RECOMMENDATION = "Only declare one constructor, preferably using the new scheme `constructor(...)` instead of `function (...)`." def _detect(self): @@ -55,7 +37,7 @@ def _detect(self): tainted_nodes=[] for contract in self.contracts: - # 判断是否有可能是uniswap相关的函数 + # 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: diff --git a/slither/detectors/functions/modifier_utils.py b/slither/detectors/functions/modifier_utils.py index 211212f1b4..54a61030fe 100644 --- a/slither/detectors/functions/modifier_utils.py +++ b/slither/detectors/functions/modifier_utils.py @@ -8,10 +8,10 @@ class ModifierUtil: @staticmethod def is_reentrancy_lock(modifier: Modifier) -> bool: """ - 是否是防重入锁 - 1、有对状态变量的读写操作 - 2、有状态变量condition语句(require、if) - 3、有placeholder:"_" + 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 @@ -56,16 +56,16 @@ def _has_msg_sender_check(func: FunctionContract): @staticmethod def is_access_control(modifier: Modifier) -> bool: """ - 是否有权限控制(onlyXXX) - 1、有placeholder:"_" - 2、有状态变量的读写操作 - 3、有包含了msg.sender的call行为语句(require、if) + 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) """ - # 不存在placeholder + # 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): From 28029854a65e76d9fb0a242caa80d651036a1e39 Mon Sep 17 00:00:00 2001 From: xueyue Date: Fri, 29 Mar 2024 14:09:42 +0800 Subject: [PATCH 3/5] update description --- slither/detectors/defi/defi_action_nested.py | 18 ++++-------------- slither/detectors/defi/k_value_error.py | 15 +++++++-------- .../detectors/functions/centralized_info.py | 14 ++++++-------- slither/detectors/functions/centralized_low.py | 13 ++++++------- .../detectors/functions/centralized_medium.py | 8 ++++---- .../detectors/functions/centralized_other.py | 8 ++++---- 6 files changed, 31 insertions(+), 45 deletions(-) diff --git a/slither/detectors/defi/defi_action_nested.py b/slither/detectors/defi/defi_action_nested.py index c7d2cf5a5b..8905a0e4a0 100644 --- a/slither/detectors/defi/defi_action_nested.py +++ b/slither/detectors/defi/defi_action_nested.py @@ -42,24 +42,14 @@ class DeFiActionNested(AbstractDetector): IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.MEDIUM - WIKI = " http:// " + WIKI = "http://" - WIKI_TITLE = "Arbitrary `from` in transferFrom used with permit" + WIKI_TITLE = "DeFi Action Nesting" WIKI_DESCRIPTION = ( - "Detect when `msg.sender` is not used as `from` in transferFrom and permit is used." + "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 = """ -```solidity - function bad(address from, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s, address to) public { - erc20.permit(from, address(this), value, deadline, v, r, s); - erc20.transferFrom(from, to, value); - } -``` -If an ERC20 token does not implement permit and has a fallback function e.g. WETH, transferFrom allows an attacker to transfer all tokens approved for this contract.""" - - WIKI_RECOMMENDATION = """ -Ensure that the underlying ERC20 token correctly implements a permit function. -""" + }""" ERC20_FUNCTION = [ "transferFrom", "safeTransferFrom", diff --git a/slither/detectors/defi/k_value_error.py b/slither/detectors/defi/k_value_error.py index fbf632a7c1..9ddab95c51 100644 --- a/slither/detectors/defi/k_value_error.py +++ b/slither/detectors/defi/k_value_error.py @@ -2,27 +2,26 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -class KValueError(AbstractDetector): +class AMMKValueError(AbstractDetector): """ - Module detecting multiple constructors in the same contract. - (This was possible prior to Solidity 0.4.23, using old and new constructor schemes). + Module detecting potential errors in K-value calculation in Automated Market Makers (AMMs). """ - ARGUMENT = "k-value-error" - HELP = "Multiple constructor schemes" + ARGUMENT = "amm-k-value-error" + HELP = "Potential K-value calculation errors" IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH WIKI = " " - WIKI_TITLE = "Multiple constructor schemes" + WIKI_TITLE = "AMM K-Value Error" WIKI_DESCRIPTION = ( - "Detect multiple constructor definitions in the same contract (using new and old schemes)." + "" ) # region wiki_exploit_scenario WIKI_EXPLOIT_SCENARIO = """""" - WIKI_RECOMMENDATION = "Only declare one constructor, preferably using the new scheme `constructor(...)` instead of `function (...)`." + WIKI_RECOMMENDATION = "Ensure correct calculation of the K-value in Automated Market Makers (AMMs) to avoid potential trading issues." def _detect(self): """ diff --git a/slither/detectors/functions/centralized_info.py b/slither/detectors/functions/centralized_info.py index 4ef83d6cde..82ca0e14f2 100644 --- a/slither/detectors/functions/centralized_info.py +++ b/slither/detectors/functions/centralized_info.py @@ -36,25 +36,23 @@ class CentralizedRiskInfo(AbstractDetector): """ - Detector for modifiers that return a default value + Detector for centralized risk in smart contracts. """ ARGUMENT = "centralized-risk-informational" - HELP = "Modifiers that can return the default value" + HELP = "Detects modifiers that may introduce centralized risk." IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH WIKI = " " - WIKI_TITLE = "Centralized Risk" - WIKI_DESCRIPTION = "aaa" + 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 = """ -If the condition in `myModif` is false, the execution of `get()` will return 0.""" + 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 = "All the paths in a modifier must execute `_` or revert." - + WIKI_RECOMMENDATION = "" def _detect(self): ''' This function is used to detect the centralized risk in the contract diff --git a/slither/detectors/functions/centralized_low.py b/slither/detectors/functions/centralized_low.py index 1747330b1a..0387a1de40 100644 --- a/slither/detectors/functions/centralized_low.py +++ b/slither/detectors/functions/centralized_low.py @@ -36,24 +36,23 @@ class CentralizedRiskLOW(AbstractDetector): """ - Detector for modifiers that return a default value + Detector for centralized risk in smart contracts with low impact and high confidence. """ ARGUMENT = "centralized-risk-low" - HELP = "Modifiers that can return the default value" + HELP = "Detects modifiers that may introduce centralized risk by returning a default value." IMPACT = DetectorClassification.LOW CONFIDENCE = DetectorClassification.HIGH WIKI = " " - WIKI_TITLE = "Centralized Risk" - WIKI_DESCRIPTION = "aaa" + 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 = """ -If the condition in `myModif` is false, the execution of `get()` will return 0.""" + WIKI_EXPLOIT_SCENARIO = """""" # endregion wiki_exploit_scenario - WIKI_RECOMMENDATION = "All the paths in a modifier must execute `_` or revert." + WIKI_RECOMMENDATION = "" def _detect(self): ''' diff --git a/slither/detectors/functions/centralized_medium.py b/slither/detectors/functions/centralized_medium.py index 02d3c4ff13..dabf7afabd 100644 --- a/slither/detectors/functions/centralized_medium.py +++ b/slither/detectors/functions/centralized_medium.py @@ -47,15 +47,15 @@ class CentralizedRiskMEDIUM(AbstractDetector): CONFIDENCE = DetectorClassification.HIGH WIKI = " " - WIKI_TITLE = "Centralized Risk" + WIKI_TITLE = "Centralized Risk with function transfer Token" WIKI_DESCRIPTION = "aaa" # region wiki_exploit_scenario - WIKI_EXPLOIT_SCENARIO = """ -If the condition in `myModif` is false, the execution of `get()` will return 0.""" + WIKI_EXPLOIT_SCENARIO = """""" # endregion wiki_exploit_scenario - WIKI_RECOMMENDATION = "All the paths in a modifier must execute `_` or revert." + WIKI_RECOMMENDATION = "" + def _detect(self): results = [] diff --git a/slither/detectors/functions/centralized_other.py b/slither/detectors/functions/centralized_other.py index 19ab487697..c24f61e65e 100644 --- a/slither/detectors/functions/centralized_other.py +++ b/slither/detectors/functions/centralized_other.py @@ -47,15 +47,15 @@ class CentralizedRiskOther(AbstractDetector): CONFIDENCE = DetectorClassification.HIGH WIKI = " " - WIKI_TITLE = "Centralized Risk" + WIKI_TITLE = "Centralized Risk with function transfer and transferFrom" WIKI_DESCRIPTION = "aaa" # region wiki_exploit_scenario - WIKI_EXPLOIT_SCENARIO = """ -If the condition in `myModif` is false, the execution of `get()` will return 0.""" + WIKI_EXPLOIT_SCENARIO = """""" # endregion wiki_exploit_scenario - WIKI_RECOMMENDATION = "All the paths in a modifier must execute `_` or revert." + WIKI_RECOMMENDATION = "" + def _detect(self): results = [] From fa736eb0518bedb22f6175d58f37e32650fb76d3 Mon Sep 17 00:00:00 2001 From: xueyue Date: Fri, 29 Mar 2024 14:34:37 +0800 Subject: [PATCH 4/5] delete irrelevant code --- .../access/OwnableUpgradeable.sol | 95 ----- .../proxy/utils/Initializable.sol | 138 ------ .../security/ReentrancyGuardUpgradeable.sol | 75 ---- .../token/ERC20/ERC20Upgradeable.sol | 395 ------------------ .../token/ERC20/IERC20Upgradeable.sol | 82 ---- .../extensions/IERC20MetadataUpgradeable.sol | 28 -- .../draft-IERC20PermitUpgradeable.sol | 60 --- .../ERC20/utils/SafeERC20Upgradeable.sol | 116 ----- .../utils/AddressUpgradeable.sol | 195 --------- .../utils/ContextUpgradeable.sol | 37 -- .../contracts/utils/math/SafeMath.sol | 227 ---------- .../contracts/interfaces/IController.sol | 10 - .../efvault/contracts/interfaces/IVault.sol | 8 - .../contracts/utils/TransferHelper.sol | 58 --- 14 files changed, 1524 deletions(-) delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts/utils/math/SafeMath.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/interfaces/IController.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/interfaces/IVault.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/utils/TransferHelper.sol diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol deleted file mode 100644 index affb7cdcb9..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol) - -pragma solidity ^0.8.0; - -import "../utils/ContextUpgradeable.sol"; -import "../proxy/utils/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 onlyInitializing { - __Ownable_init_unchained(); - } - - function __Ownable_init_unchained() internal onlyInitializing { - _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); - } - - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - */ - uint256[49] private __gap; -} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol deleted file mode 100644 index 3aa7535dac..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol +++ /dev/null @@ -1,138 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (proxy/utils/Initializable.sol) - -pragma solidity ^0.8.2; - -import "../../utils/AddressUpgradeable.sol"; - -/** - * @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 proxied contracts do not make use of 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. - * - * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be - * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in - * case an upgrade adds a module that needs to be initialized. - * - * For example: - * - * [.hljs-theme-light.nopadding] - * ``` - * contract MyToken is ERC20Upgradeable { - * function initialize() initializer public { - * __ERC20_init("MyToken", "MTK"); - * } - * } - * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { - * function initializeV2() reinitializer(2) public { - * __ERC20Permit_init("MyToken"); - * } - * } - * ``` - * - * 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. - * - * [CAUTION] - * ==== - * Avoid leaving a contract uninitialized. - * - * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation - * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke - * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: - * - * [.hljs-theme-light.nopadding] - * ``` - * /// @custom:oz-upgrades-unsafe-allow constructor - * constructor() { - * _disableInitializers(); - * } - * ``` - * ==== - */ -abstract contract Initializable { - /** - * @dev Indicates that the contract has been initialized. - * @custom:oz-retyped-from bool - */ - uint8 private _initialized; - - /** - * @dev Indicates that the contract is in the process of being initialized. - */ - bool private _initializing; - - /** - * @dev Triggered when the contract has been initialized or reinitialized. - */ - event Initialized(uint8 version); - - /** - * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, - * `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`. - */ - modifier initializer() { - bool isTopLevelCall = !_initializing; - require( - (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1), - "Initializable: contract is already initialized" - ); - _initialized = 1; - if (isTopLevelCall) { - _initializing = true; - } - _; - if (isTopLevelCall) { - _initializing = false; - emit Initialized(1); - } - } - - /** - * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the - * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be - * used to initialize parent contracts. - * - * `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original - * initialization step. This is essential to configure modules that are added through upgrades and that require - * initialization. - * - * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in - * a contract, executing them in the right order is up to the developer or operator. - */ - modifier reinitializer(uint8 version) { - require(!_initializing && _initialized < version, "Initializable: contract is already initialized"); - _initialized = version; - _initializing = true; - _; - _initializing = false; - emit Initialized(version); - } - - /** - * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the - * {initializer} and {reinitializer} modifiers, directly or indirectly. - */ - modifier onlyInitializing() { - require(_initializing, "Initializable: contract is not initializing"); - _; - } - - /** - * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. - * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized - * to any version. It is recommended to use this to lock implementation contracts that are designed to be called - * through proxies. - */ - function _disableInitializers() internal virtual { - require(!_initializing, "Initializable: contract is initializing"); - if (_initialized < type(uint8).max) { - _initialized = type(uint8).max; - emit Initialized(type(uint8).max); - } - } -} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol deleted file mode 100644 index 2a34a67ab1..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol) - -pragma solidity ^0.8.0; -import "../proxy/utils/Initializable.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 ReentrancyGuardUpgradeable is Initializable { - // 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; - - function __ReentrancyGuard_init() internal onlyInitializing { - __ReentrancyGuard_init_unchained(); - } - - function __ReentrancyGuard_init_unchained() internal onlyInitializing { - _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 making 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 This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - */ - uint256[49] private __gap; -} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol deleted file mode 100644 index f544f0a515..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol +++ /dev/null @@ -1,395 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol) - -pragma solidity ^0.8.0; - -import "./IERC20Upgradeable.sol"; -import "./extensions/IERC20MetadataUpgradeable.sol"; -import "../../utils/ContextUpgradeable.sol"; -import "../../proxy/utils/Initializable.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 ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable { - 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. - */ - function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing { - __ERC20_init_unchained(name_, symbol_); - } - - function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing { - _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 {} - - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - */ - uint256[45] private __gap; -} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol deleted file mode 100644 index f72ebe9676..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: MIT -// 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 IERC20Upgradeable { - /** - * @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); -} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol deleted file mode 100644 index 381d8ea2a1..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) - -pragma solidity ^0.8.0; - -import "../IERC20Upgradeable.sol"; - -/** - * @dev Interface for the optional metadata functions from the ERC20 standard. - * - * _Available since v4.1._ - */ -interface IERC20MetadataUpgradeable is IERC20Upgradeable { - /** - * @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); -} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol deleted file mode 100644 index bd5751322e..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol) - -pragma solidity ^0.8.0; - -/** - * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in - * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. - * - * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by - * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't - * need to send a transaction, and thus is not required to hold Ether at all. - */ -interface IERC20PermitUpgradeable { - /** - * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, - * given ``owner``'s signed approval. - * - * IMPORTANT: The same issues {IERC20-approve} has related to transaction - * ordering also apply here. - * - * Emits an {Approval} event. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `deadline` must be a timestamp in the future. - * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` - * over the EIP712-formatted function arguments. - * - the signature must use ``owner``'s current nonce (see {nonces}). - * - * For more information on the signature format, see the - * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP - * section]. - */ - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @dev Returns the current nonce for `owner`. This value must be - * included whenever a signature is generated for {permit}. - * - * Every successful call to {permit} increases ``owner``'s nonce by one. This - * prevents a signature from being used multiple times. - */ - function nonces(address owner) external view returns (uint256); - - /** - * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. - */ - // solhint-disable-next-line func-name-mixedcase - function DOMAIN_SEPARATOR() external view returns (bytes32); -} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol deleted file mode 100644 index 65d2fc1a7a..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/utils/SafeERC20.sol) - -pragma solidity ^0.8.0; - -import "../IERC20Upgradeable.sol"; -import "../extensions/draft-IERC20PermitUpgradeable.sol"; -import "../../../utils/AddressUpgradeable.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 SafeERC20Upgradeable { - using AddressUpgradeable for address; - - function safeTransfer( - IERC20Upgradeable token, - address to, - uint256 value - ) internal { - _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); - } - - function safeTransferFrom( - IERC20Upgradeable 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( - IERC20Upgradeable 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( - IERC20Upgradeable 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( - IERC20Upgradeable 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)); - } - } - - function safePermit( - IERC20PermitUpgradeable token, - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) internal { - uint256 nonceBefore = token.nonces(owner); - token.permit(owner, spender, value, deadline, v, r, s); - uint256 nonceAfter = token.nonces(owner); - require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed"); - } - - /** - * @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(IERC20Upgradeable 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"); - } - } -} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol deleted file mode 100644 index 124513390b..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol +++ /dev/null @@ -1,195 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol) - -pragma solidity ^0.8.1; - -/** - * @dev Collection of functions related to the address type - */ -library AddressUpgradeable { - /** - * @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 - * ==== - * - * [IMPORTANT] - * ==== - * You shouldn't rely on `isContract` to protect against flash loan attacks! - * - * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets - * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract - * constructor. - * ==== - */ - function isContract(address account) internal view returns (bool) { - // This method relies on extcodesize/address.code.length, which returns 0 - // for contracts in construction, since the code is only stored at the end - // of the constructor execution. - - return account.code.length > 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 Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the - * revert reason using the provided one. - * - * _Available since v4.3._ - */ - function verifyCallResult( - bool success, - bytes memory returndata, - string memory errorMessage - ) internal 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 - /// @solidity memory-safe-assembly - assembly { - let returndata_size := mload(returndata) - revert(add(32, returndata), returndata_size) - } - } else { - revert(errorMessage); - } - } - } -} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol deleted file mode 100644 index 481064e415..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) - -pragma solidity ^0.8.0; -import "../proxy/utils/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 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 onlyInitializing { - } - - function __Context_init_unchained() internal onlyInitializing { - } - function _msgSender() internal view virtual returns (address) { - return msg.sender; - } - - function _msgData() internal view virtual returns (bytes calldata) { - return msg.data; - } - - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - */ - uint256[50] private __gap; -} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts/utils/math/SafeMath.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts/utils/math/SafeMath.sol deleted file mode 100644 index 550f0e779b..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/@openzeppelin/contracts/utils/math/SafeMath.sol +++ /dev/null @@ -1,227 +0,0 @@ -// SPDX-License-Identifier: MIT -// 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; - } - } -} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/interfaces/IController.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/interfaces/IController.sol deleted file mode 100644 index 3fd249cf0c..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/interfaces/IController.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IController { - function totalAssets() external view returns (uint256); - - function deposit(uint256 _amount) external returns (uint256); - - function withdraw(uint256 _amount, address _receiver) external returns (uint256, uint256); -} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/interfaces/IVault.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/interfaces/IVault.sol deleted file mode 100644 index d722b91984..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/interfaces/IVault.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IVault { - function deposit(uint256 assets, address receiver) external payable returns (uint256 shares); - - function mint(uint256 amount, address account) external; -} diff --git a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/utils/TransferHelper.sol b/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/utils/TransferHelper.sol deleted file mode 100644 index 041c08a263..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-info/0.8.2/efvault/contracts/utils/TransferHelper.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -pragma solidity ^0.8.0; - - -// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false -library TransferHelper { - function safeApprove( - address token, - address to, - uint256 value - ) internal { - // bytes4(keccak256(bytes('approve(address,uint256)'))); - (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value)); - require(success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper: APPROVE_FAILED"); - } - - /** - Transfer Util for both Ether and ERC20 - */ - function safeTransfer( - address token, - address to, - uint256 value - ) internal { - if (address(token) == address(0)) { - safeTransferETH(to, value); - } else { - safeTransferToken(address(token), to, value); - } - } - - function safeTransferToken( - address token, - address to, - uint256 value - ) internal { - // bytes4(keccak256(bytes('transfer(address,uint256)'))); - (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value)); - require(success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper: TRANSFER_FAILED"); - } - - function safeTransferFrom( - address token, - address from, - address to, - uint256 value - ) internal { - // bytes4(keccak256(bytes('transferFrom(address,address,uint256)'))); - (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value)); - require(success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper: TRANSFER_FROM_FAILED"); - } - - function safeTransferETH(address to, uint256 value) internal { - (bool success, ) = to.call{value: value}(new bytes(0)); - require(success, "TransferHelper: ETH_TRANSFER_FAILED"); - } -} From 020c2e88f341a80285ffafdbd61eae9e58ba85d8 Mon Sep 17 00:00:00 2001 From: xueyue Date: Fri, 29 Mar 2024 14:36:53 +0800 Subject: [PATCH 5/5] delete irrelevant test case --- .../price-manipulation-high/0.5.12/Issuer.sol | 2730 ----------------- .../price-manipulation-high/0.6.11/BConst.sol | 62 - .../price-manipulation-high/0.6.11/BMath.sol | 252 -- .../price-manipulation-high/0.6.11/BNum.sol | 137 - .../price-manipulation-high/0.6.11/BToken.sol | 192 -- .../0.6.11/BeefyVault.sol | 941 ------ .../0.6.11/ICompLikeToken.sol | 7 - .../0.6.11/IIndexPool.sol | 202 -- .../0.6.11/IndexPool.sol | 1368 --------- .../0.6.11/SafeMath.sol | 159 - .../0.6.11/Safemoon.sol | 1166 ------- .../0.6.11/SpaceGoat.sol | 1198 -------- .../0.6.11/BConst.sol | 62 - .../0.6.11/BMath.sol | 252 -- .../price-manipulation-medium/0.6.11/BNum.sol | 137 - .../0.6.11/BToken.sol | 192 -- .../0.6.11/BeefyVault.sol | 941 ------ .../0.6.11/ICompLikeToken.sol | 7 - .../0.6.11/IIndexPool.sol | 202 -- .../0.6.11/IndexPool.sol | 1368 --------- .../0.6.11/SafeMath.sol | 159 - .../0.6.11/Safemoon.sol | 1166 ------- .../0.6.11/SpaceGoat.sol | 1198 -------- 23 files changed, 14098 deletions(-) delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.5.12/Issuer.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BConst.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BMath.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BNum.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BToken.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BeefyVault.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/ICompLikeToken.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/IIndexPool.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/IndexPool.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/SafeMath.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/Safemoon.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/SpaceGoat.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BConst.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BMath.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BNum.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BToken.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BeefyVault.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/ICompLikeToken.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/IIndexPool.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/IndexPool.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/SafeMath.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/Safemoon.sol delete mode 100644 tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/SpaceGoat.sol diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.5.12/Issuer.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.5.12/Issuer.sol deleted file mode 100644 index 7fa8525109..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-high/0.5.12/Issuer.sol +++ /dev/null @@ -1,2730 +0,0 @@ -/** - *Submitted for verification at BscScan.com on 2021-08-02 -*/ - -/* - ___ _ ___ _ - | .\ ___ _ _ <_> ___ | __><_>._ _ ___ ._ _ ___ ___ - | _// ._>| '_>| ||___|| _> | || ' |<_> || ' |/ | '/ ._> - |_| \___.|_| |_| |_| |_||_|_|<___||_|_|\_|_.\___. - -* PeriFinance: Issuer.sol -* -* Latest source (may be newer): https://github.com/perifinance/peri-finance/blob/master/contracts/Issuer.sol -* Docs: Will be added in the future. -* https://docs.peri.finance/contracts/source/contracts/Issuer -* -* Contract Dependencies: -* - IAddressResolver -* - IIssuer -* - MixinResolver -* - MixinSystemSettings -* - Owned -* Libraries: -* - SafeDecimalMath -* - SafeMath -* -* MIT License -* =========== -* -* Copyright (c) 2021 PeriFinance -* -* 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 -*/ - - - -pragma solidity ^0.5.0; - -// https://docs.peri.finance/contracts/source/contracts/owned -contract Owned { - address public owner; - address public nominatedOwner; - - constructor(address _owner) public { - require(_owner != address(0), "Owner address cannot be 0"); - owner = _owner; - emit OwnerChanged(address(0), _owner); - } - - function nominateNewOwner(address _owner) external onlyOwner { - nominatedOwner = _owner; - emit OwnerNominated(_owner); - } - - function acceptOwnership() external { - require(msg.sender == nominatedOwner, "You must be nominated before you can accept ownership"); - emit OwnerChanged(owner, nominatedOwner); - owner = nominatedOwner; - nominatedOwner = address(0); - } - - modifier onlyOwner { - _onlyOwner(); - _; - } - - function _onlyOwner() private view { - require(msg.sender == owner, "Only the contract owner may perform this action"); - } - - event OwnerNominated(address newOwner); - event OwnerChanged(address oldOwner, address newOwner); -} - - -// https://docs.peri.finance/contracts/source/interfaces/iaddressresolver -interface IAddressResolver { - function getAddress(bytes32 name) external view returns (address); - - function getPynth(bytes32 key) external view returns (address); - - function requireAndGetAddress(bytes32 name, string calldata reason) external view returns (address); -} - - -// https://docs.peri.finance/contracts/source/interfaces/ipynth -interface IPynth { - // Views - function currencyKey() external view returns (bytes32); - - function transferablePynths(address account) external view returns (uint); - - // Mutative functions - function transferAndSettle(address to, uint value) external returns (bool); - - function transferFromAndSettle( - address from, - address to, - uint value - ) external returns (bool); - - // Restricted: used internally to PeriFinance - function burn(address account, uint amount) external; - - function issue(address account, uint amount) external; -} - - -// https://docs.peri.finance/contracts/source/interfaces/iissuer -interface IIssuer { - // Views - function anyPynthOrPERIRateIsInvalid() external view returns (bool anyRateInvalid); - - function availableCurrencyKeys() external view returns (bytes32[] memory); - - function availablePynthCount() external view returns (uint); - - function availablePynths(uint index) external view returns (IPynth); - - function canBurnPynths(address account) external view returns (bool); - - function collateral(address account) external view returns (uint); - - function collateralisationRatio(address issuer) external view returns (uint); - - function collateralisationRatioAndAnyRatesInvalid(address _issuer) - external - view - returns (uint cratio, bool anyRateIsInvalid); - - function debtBalanceOf(address issuer, bytes32 currencyKey) external view returns (uint debtBalance); - - function issuanceRatio() external view returns (uint); - - function externalTokenLimit() external view returns (uint); - - function lastIssueEvent(address account) external view returns (uint); - - function maxIssuablePynths(address issuer) external view returns (uint maxIssuable); - - function externalTokenQuota( - address _account, - uint _addtionalpUSD, - uint _addtionalExToken, - bool _isIssue - ) external view returns (uint); - - function maxExternalTokenStakeAmount(address _account, bytes32 _currencyKey) - external - view - returns (uint issueAmountToQuota, uint stakeAmountToQuota); - - function minimumStakeTime() external view returns (uint); - - function remainingIssuablePynths(address issuer) - external - view - returns ( - uint maxIssuable, - uint alreadyIssued, - uint totalSystemDebt - ); - - function pynths(bytes32 currencyKey) external view returns (IPynth); - - function getPynths(bytes32[] calldata currencyKeys) external view returns (IPynth[] memory); - - function pynthsByAddress(address pynthAddress) external view returns (bytes32); - - function totalIssuedPynths(bytes32 currencyKey, bool excludeEtherCollateral) external view returns (uint); - - function transferablePeriFinanceAndAnyRateIsInvalid(address account, uint balance) - external - view - returns (uint transferable, bool anyRateIsInvalid); - - // Restricted: used internally to PeriFinance - function issuePynths( - address _issuer, - bytes32 _currencyKey, - uint _issueAmount - ) external; - - function issueMaxPynths(address _issuer) external; - - function issuePynthsToMaxQuota(address _issuer, bytes32 _currencyKey) external; - - function burnPynths( - address _from, - bytes32 _currencyKey, - uint _burnAmount - ) external; - - function fitToClaimable(address _from) external; - - function exit(address _from) external; - - function liquidateDelinquentAccount( - address account, - uint pusdAmount, - address liquidator - ) external returns (uint totalRedeemed, uint amountToLiquidate); -} - - -// Inheritance - - -// Internal references - - -// https://docs.peri.finance/contracts/source/contracts/addressresolver -contract AddressResolver is Owned, IAddressResolver { - mapping(bytes32 => address) public repository; - - constructor(address _owner) public Owned(_owner) {} - - /* ========== RESTRICTED FUNCTIONS ========== */ - - function importAddresses(bytes32[] calldata names, address[] calldata destinations) external onlyOwner { - require(names.length == destinations.length, "Input lengths must match"); - - for (uint i = 0; i < names.length; i++) { - bytes32 name = names[i]; - address destination = destinations[i]; - repository[name] = destination; - emit AddressImported(name, destination); - } - } - - /* ========= PUBLIC FUNCTIONS ========== */ - - function rebuildCaches(MixinResolver[] calldata destinations) external { - for (uint i = 0; i < destinations.length; i++) { - destinations[i].rebuildCache(); - } - } - - /* ========== VIEWS ========== */ - - function areAddressesImported(bytes32[] calldata names, address[] calldata destinations) external view returns (bool) { - for (uint i = 0; i < names.length; i++) { - if (repository[names[i]] != destinations[i]) { - return false; - } - } - return true; - } - - function getAddress(bytes32 name) external view returns (address) { - return repository[name]; - } - - function requireAndGetAddress(bytes32 name, string calldata reason) external view returns (address) { - address _foundAddress = repository[name]; - require(_foundAddress != address(0), reason); - return _foundAddress; - } - - function getPynth(bytes32 key) external view returns (address) { - IIssuer issuer = IIssuer(repository["Issuer"]); - require(address(issuer) != address(0), "Cannot find Issuer address"); - return address(issuer.pynths(key)); - } - - /* ========== EVENTS ========== */ - - event AddressImported(bytes32 name, address destination); -} - - -// solhint-disable payable-fallback - -// https://docs.peri.finance/contracts/source/contracts/readproxy -contract ReadProxy is Owned { - address public target; - - constructor(address _owner) public Owned(_owner) {} - - function setTarget(address _target) external onlyOwner { - target = _target; - emit TargetUpdated(target); - } - - function() external { - // The basics of a proxy read call - // Note that msg.sender in the underlying will always be the address of this contract. - assembly { - calldatacopy(0, 0, calldatasize) - - // Use of staticcall - this will revert if the underlying function mutates state - let result := staticcall(gas, sload(target_slot), 0, calldatasize, 0, 0) - returndatacopy(0, 0, returndatasize) - - if iszero(result) { - revert(0, returndatasize) - } - return(0, returndatasize) - } - } - - event TargetUpdated(address newTarget); -} - - -// Inheritance - - -// Internal references - - -// https://docs.peri.finance/contracts/source/contracts/mixinresolver -contract MixinResolver { - AddressResolver public resolver; - - mapping(bytes32 => address) private addressCache; - - constructor(address _resolver) internal { - resolver = AddressResolver(_resolver); - } - - /* ========== INTERNAL FUNCTIONS ========== */ - - function combineArrays(bytes32[] memory first, bytes32[] memory second) - internal - pure - returns (bytes32[] memory combination) - { - combination = new bytes32[](first.length + second.length); - - for (uint i = 0; i < first.length; i++) { - combination[i] = first[i]; - } - - for (uint j = 0; j < second.length; j++) { - combination[first.length + j] = second[j]; - } - } - - /* ========== PUBLIC FUNCTIONS ========== */ - - // Note: this function is public not external in order for it to be overridden and invoked via super in subclasses - function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {} - - function rebuildCache() public { - bytes32[] memory requiredAddresses = resolverAddressesRequired(); - // The resolver must call this function whenver it updates its state - for (uint i = 0; i < requiredAddresses.length; i++) { - bytes32 name = requiredAddresses[i]; - // Note: can only be invoked once the resolver has all the targets needed added - address destination = - resolver.requireAndGetAddress(name, string(abi.encodePacked("Resolver missing target: ", name))); - addressCache[name] = destination; - emit CacheUpdated(name, destination); - } - } - - /* ========== VIEWS ========== */ - - function isResolverCached() external view returns (bool) { - bytes32[] memory requiredAddresses = resolverAddressesRequired(); - for (uint i = 0; i < requiredAddresses.length; i++) { - bytes32 name = requiredAddresses[i]; - // false if our cache is invalid or if the resolver doesn't have the required address - if (resolver.getAddress(name) != addressCache[name] || addressCache[name] == address(0)) { - return false; - } - } - - return true; - } - - /* ========== INTERNAL FUNCTIONS ========== */ - - function requireAndGetAddress(bytes32 name) internal view returns (address) { - address _foundAddress = addressCache[name]; - require(_foundAddress != address(0), string(abi.encodePacked("Missing address: ", name))); - return _foundAddress; - } - - /* ========== EVENTS ========== */ - - event CacheUpdated(bytes32 name, address destination); -} - - -// https://docs.peri.finance/contracts/source/interfaces/iflexiblestorage -interface IFlexibleStorage { - // Views - function getUIntValue(bytes32 contractName, bytes32 record) external view returns (uint); - - function getUIntValues(bytes32 contractName, bytes32[] calldata records) external view returns (uint[] memory); - - function getIntValue(bytes32 contractName, bytes32 record) external view returns (int); - - function getIntValues(bytes32 contractName, bytes32[] calldata records) external view returns (int[] memory); - - function getAddressValue(bytes32 contractName, bytes32 record) external view returns (address); - - function getAddressValues(bytes32 contractName, bytes32[] calldata records) external view returns (address[] memory); - - function getBoolValue(bytes32 contractName, bytes32 record) external view returns (bool); - - function getBoolValues(bytes32 contractName, bytes32[] calldata records) external view returns (bool[] memory); - - function getBytes32Value(bytes32 contractName, bytes32 record) external view returns (bytes32); - - function getBytes32Values(bytes32 contractName, bytes32[] calldata records) external view returns (bytes32[] memory); - - // Mutative functions - function deleteUIntValue(bytes32 contractName, bytes32 record) external; - - function deleteIntValue(bytes32 contractName, bytes32 record) external; - - function deleteAddressValue(bytes32 contractName, bytes32 record) external; - - function deleteBoolValue(bytes32 contractName, bytes32 record) external; - - function deleteBytes32Value(bytes32 contractName, bytes32 record) external; - - function setUIntValue( - bytes32 contractName, - bytes32 record, - uint value - ) external; - - function setUIntValues( - bytes32 contractName, - bytes32[] calldata records, - uint[] calldata values - ) external; - - function setIntValue( - bytes32 contractName, - bytes32 record, - int value - ) external; - - function setIntValues( - bytes32 contractName, - bytes32[] calldata records, - int[] calldata values - ) external; - - function setAddressValue( - bytes32 contractName, - bytes32 record, - address value - ) external; - - function setAddressValues( - bytes32 contractName, - bytes32[] calldata records, - address[] calldata values - ) external; - - function setBoolValue( - bytes32 contractName, - bytes32 record, - bool value - ) external; - - function setBoolValues( - bytes32 contractName, - bytes32[] calldata records, - bool[] calldata values - ) external; - - function setBytes32Value( - bytes32 contractName, - bytes32 record, - bytes32 value - ) external; - - function setBytes32Values( - bytes32 contractName, - bytes32[] calldata records, - bytes32[] calldata values - ) external; -} - - -// Internal references - - -// https://docs.peri.finance/contracts/source/contracts/mixinsystemsettings -contract MixinSystemSettings is MixinResolver { - bytes32 internal constant SETTING_CONTRACT_NAME = "SystemSettings"; - - bytes32 internal constant SETTING_WAITING_PERIOD_SECS = "waitingPeriodSecs"; - bytes32 internal constant SETTING_PRICE_DEVIATION_THRESHOLD_FACTOR = "priceDeviationThresholdFactor"; - bytes32 internal constant SETTING_ISSUANCE_RATIO = "issuanceRatio"; - bytes32 internal constant SETTING_FEE_PERIOD_DURATION = "feePeriodDuration"; - bytes32 internal constant SETTING_TARGET_THRESHOLD = "targetThreshold"; - bytes32 internal constant SETTING_LIQUIDATION_DELAY = "liquidationDelay"; - bytes32 internal constant SETTING_LIQUIDATION_RATIO = "liquidationRatio"; - bytes32 internal constant SETTING_LIQUIDATION_PENALTY = "liquidationPenalty"; - bytes32 internal constant SETTING_RATE_STALE_PERIOD = "rateStalePeriod"; - bytes32 internal constant SETTING_EXCHANGE_FEE_RATE = "exchangeFeeRate"; - bytes32 internal constant SETTING_MINIMUM_STAKE_TIME = "minimumStakeTime"; - bytes32 internal constant SETTING_AGGREGATOR_WARNING_FLAGS = "aggregatorWarningFlags"; - bytes32 internal constant SETTING_TRADING_REWARDS_ENABLED = "tradingRewardsEnabled"; - bytes32 internal constant SETTING_DEBT_SNAPSHOT_STALE_TIME = "debtSnapshotStaleTime"; - bytes32 internal constant SETTING_CROSS_DOMAIN_DEPOSIT_GAS_LIMIT = "crossDomainDepositGasLimit"; - bytes32 internal constant SETTING_CROSS_DOMAIN_ESCROW_GAS_LIMIT = "crossDomainEscrowGasLimit"; - bytes32 internal constant SETTING_CROSS_DOMAIN_REWARD_GAS_LIMIT = "crossDomainRewardGasLimit"; - bytes32 internal constant SETTING_CROSS_DOMAIN_WITHDRAWAL_GAS_LIMIT = "crossDomainWithdrawalGasLimit"; - bytes32 internal constant SETTING_EXTERNAL_TOKEN_QUOTA = "externalTokenQuota"; - - bytes32 internal constant CONTRACT_FLEXIBLESTORAGE = "FlexibleStorage"; - - enum CrossDomainMessageGasLimits {Deposit, Escrow, Reward, Withdrawal} - - constructor(address _resolver) internal MixinResolver(_resolver) {} - - function resolverAddressesRequired() public view returns (bytes32[] memory addresses) { - addresses = new bytes32[](1); - addresses[0] = CONTRACT_FLEXIBLESTORAGE; - } - - function flexibleStorage() internal view returns (IFlexibleStorage) { - return IFlexibleStorage(requireAndGetAddress(CONTRACT_FLEXIBLESTORAGE)); - } - - function _getGasLimitSetting(CrossDomainMessageGasLimits gasLimitType) internal pure returns (bytes32) { - if (gasLimitType == CrossDomainMessageGasLimits.Deposit) { - return SETTING_CROSS_DOMAIN_DEPOSIT_GAS_LIMIT; - } else if (gasLimitType == CrossDomainMessageGasLimits.Escrow) { - return SETTING_CROSS_DOMAIN_ESCROW_GAS_LIMIT; - } else if (gasLimitType == CrossDomainMessageGasLimits.Reward) { - return SETTING_CROSS_DOMAIN_REWARD_GAS_LIMIT; - } else if (gasLimitType == CrossDomainMessageGasLimits.Withdrawal) { - return SETTING_CROSS_DOMAIN_WITHDRAWAL_GAS_LIMIT; - } else { - revert("Unknown gas limit type"); - } - } - - function getCrossDomainMessageGasLimit(CrossDomainMessageGasLimits gasLimitType) internal view returns (uint) { - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, _getGasLimitSetting(gasLimitType)); - } - - function getTradingRewardsEnabled() internal view returns (bool) { - return flexibleStorage().getBoolValue(SETTING_CONTRACT_NAME, SETTING_TRADING_REWARDS_ENABLED); - } - - function getWaitingPeriodSecs() internal view returns (uint) { - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_WAITING_PERIOD_SECS); - } - - function getPriceDeviationThresholdFactor() internal view returns (uint) { - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_PRICE_DEVIATION_THRESHOLD_FACTOR); - } - - function getIssuanceRatio() internal view returns (uint) { - // lookup on flexible storage directly for gas savings (rather than via SystemSettings) - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ISSUANCE_RATIO); - } - - function getFeePeriodDuration() internal view returns (uint) { - // lookup on flexible storage directly for gas savings (rather than via SystemSettings) - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_FEE_PERIOD_DURATION); - } - - function getTargetThreshold() internal view returns (uint) { - // lookup on flexible storage directly for gas savings (rather than via SystemSettings) - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_TARGET_THRESHOLD); - } - - function getLiquidationDelay() internal view returns (uint) { - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_DELAY); - } - - function getLiquidationRatio() internal view returns (uint) { - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_RATIO); - } - - function getLiquidationPenalty() internal view returns (uint) { - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_PENALTY); - } - - function getRateStalePeriod() internal view returns (uint) { - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_RATE_STALE_PERIOD); - } - - function getExchangeFeeRate(bytes32 currencyKey) internal view returns (uint) { - return - flexibleStorage().getUIntValue( - SETTING_CONTRACT_NAME, - keccak256(abi.encodePacked(SETTING_EXCHANGE_FEE_RATE, currencyKey)) - ); - } - - function getMinimumStakeTime() internal view returns (uint) { - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_MINIMUM_STAKE_TIME); - } - - function getAggregatorWarningFlags() internal view returns (address) { - return flexibleStorage().getAddressValue(SETTING_CONTRACT_NAME, SETTING_AGGREGATOR_WARNING_FLAGS); - } - - function getDebtSnapshotStaleTime() internal view returns (uint) { - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_DEBT_SNAPSHOT_STALE_TIME); - } - - function getExternalTokenQuota() internal view returns (uint) { - return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_EXTERNAL_TOKEN_QUOTA); - } -} - - -/** - * @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) { - require(b <= a, "SafeMath: subtraction overflow"); - 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-solidity/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) { - // Solidity only automatically asserts when dividing by 0 - require(b > 0, "SafeMath: division by zero"); - 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) { - require(b != 0, "SafeMath: modulo by zero"); - return a % b; - } -} - - -// Libraries - - -// https://docs.peri.finance/contracts/source/libraries/safedecimalmath -library SafeDecimalMath { - using SafeMath for uint; - - /* Number of decimal places in the representations. */ - uint8 public constant decimals = 18; - uint8 public constant highPrecisionDecimals = 27; - - /* The number representing 1.0. */ - uint public constant UNIT = 10**uint(decimals); - - /* The number representing 1.0 for higher fidelity numbers. */ - uint public constant PRECISE_UNIT = 10**uint(highPrecisionDecimals); - uint private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 10**uint(highPrecisionDecimals - decimals); - - /** - * @return Provides an interface to UNIT. - */ - function unit() external pure returns (uint) { - return UNIT; - } - - /** - * @return Provides an interface to PRECISE_UNIT. - */ - function preciseUnit() external pure returns (uint) { - return PRECISE_UNIT; - } - - /** - * @return The result of multiplying x and y, interpreting the operands as fixed-point - * decimals. - * - * @dev A unit factor is divided out after the product of x and y is evaluated, - * so that product must be less than 2**256. As this is an integer division, - * the internal division always rounds down. This helps save on gas. Rounding - * is more expensive on gas. - */ - function multiplyDecimal(uint x, uint y) internal pure returns (uint) { - /* Divide by UNIT to remove the extra factor introduced by the product. */ - return x.mul(y) / UNIT; - } - - /** - * @return The result of safely multiplying x and y, interpreting the operands - * as fixed-point decimals of the specified precision unit. - * - * @dev The operands should be in the form of a the specified unit factor which will be - * divided out after the product of x and y is evaluated, so that product must be - * less than 2**256. - * - * Unlike multiplyDecimal, this function rounds the result to the nearest increment. - * Rounding is useful when you need to retain fidelity for small decimal numbers - * (eg. small fractions or percentages). - */ - function _multiplyDecimalRound( - uint x, - uint y, - uint precisionUnit - ) private pure returns (uint) { - /* Divide by UNIT to remove the extra factor introduced by the product. */ - uint quotientTimesTen = x.mul(y) / (precisionUnit / 10); - - if (quotientTimesTen % 10 >= 5) { - quotientTimesTen += 10; - } - - return quotientTimesTen / 10; - } - - /** - * @return The result of safely multiplying x and y, interpreting the operands - * as fixed-point decimals of a precise unit. - * - * @dev The operands should be in the precise unit factor which will be - * divided out after the product of x and y is evaluated, so that product must be - * less than 2**256. - * - * Unlike multiplyDecimal, this function rounds the result to the nearest increment. - * Rounding is useful when you need to retain fidelity for small decimal numbers - * (eg. small fractions or percentages). - */ - function multiplyDecimalRoundPrecise(uint x, uint y) internal pure returns (uint) { - return _multiplyDecimalRound(x, y, PRECISE_UNIT); - } - - /** - * @return The result of safely multiplying x and y, interpreting the operands - * as fixed-point decimals of a standard unit. - * - * @dev The operands should be in the standard unit factor which will be - * divided out after the product of x and y is evaluated, so that product must be - * less than 2**256. - * - * Unlike multiplyDecimal, this function rounds the result to the nearest increment. - * Rounding is useful when you need to retain fidelity for small decimal numbers - * (eg. small fractions or percentages). - */ - function multiplyDecimalRound(uint x, uint y) internal pure returns (uint) { - return _multiplyDecimalRound(x, y, UNIT); - } - - /** - * @return The result of safely dividing x and y. The return value is a high - * precision decimal. - * - * @dev y is divided after the product of x and the standard precision unit - * is evaluated, so the product of x and UNIT must be less than 2**256. As - * this is an integer division, the result is always rounded down. - * This helps save on gas. Rounding is more expensive on gas. - */ - function divideDecimal(uint x, uint y) internal pure returns (uint) { - /* Reintroduce the UNIT factor that will be divided out by y. */ - return x.mul(UNIT).div(y); - } - - /** - * @return The result of safely dividing x and y. The return value is as a rounded - * decimal in the precision unit specified in the parameter. - * - * @dev y is divided after the product of x and the specified precision unit - * is evaluated, so the product of x and the specified precision unit must - * be less than 2**256. The result is rounded to the nearest increment. - */ - function _divideDecimalRound( - uint x, - uint y, - uint precisionUnit - ) private pure returns (uint) { - uint resultTimesTen = x.mul(precisionUnit * 10).div(y); - - if (resultTimesTen % 10 >= 5) { - resultTimesTen += 10; - } - - return resultTimesTen / 10; - } - - /** - * @return The result of safely dividing x and y. The return value is as a rounded - * standard precision decimal. - * - * @dev y is divided after the product of x and the standard precision unit - * is evaluated, so the product of x and the standard precision unit must - * be less than 2**256. The result is rounded to the nearest increment. - */ - function divideDecimalRound(uint x, uint y) internal pure returns (uint) { - return _divideDecimalRound(x, y, UNIT); - } - - /** - * @return The result of safely dividing x and y. The return value is as a rounded - * high precision decimal. - * - * @dev y is divided after the product of x and the high precision unit - * is evaluated, so the product of x and the high precision unit must - * be less than 2**256. The result is rounded to the nearest increment. - */ - function divideDecimalRoundPrecise(uint x, uint y) internal pure returns (uint) { - return _divideDecimalRound(x, y, PRECISE_UNIT); - } - - /** - * @dev Convert a standard decimal representation to a high precision one. - */ - function decimalToPreciseDecimal(uint i) internal pure returns (uint) { - return i.mul(UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR); - } - - /** - * @dev Convert a high precision decimal to a standard decimal representation. - */ - function preciseDecimalToDecimal(uint i) internal pure returns (uint) { - uint quotientTimesTen = i / (UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR / 10); - - if (quotientTimesTen % 10 >= 5) { - quotientTimesTen += 10; - } - - return quotientTimesTen / 10; - } - - /** - * @dev Round down the value with given number - */ - function roundDownDecimal(uint x, uint d) internal pure returns (uint) { - return x.div(10**d).mul(10**d); - } - - /** - * @dev Round up the value with given number - */ - function roundUpDecimal(uint x, uint d) internal pure returns (uint) { - uint _decimal = 10**d; - - if (x % _decimal > 0) { - x = x.add(10**d); - } - - return x.div(_decimal).mul(_decimal); - } -} - - -interface IVirtualPynth { - // Views - function balanceOfUnderlying(address account) external view returns (uint); - - function rate() external view returns (uint); - - function readyToSettle() external view returns (bool); - - function secsLeftInWaitingPeriod() external view returns (uint); - - function settled() external view returns (bool); - - function pynth() external view returns (IPynth); - - // Mutative functions - function settle(address account) external; -} - - -// https://docs.peri.finance/contracts/source/interfaces/iperiFinance -interface IPeriFinance { - // Views - function getRequiredAddress(bytes32 contractName) external view returns (address); - - function anyPynthOrPERIRateIsInvalid() external view returns (bool anyRateInvalid); - - function availableCurrencyKeys() external view returns (bytes32[] memory); - - function availablePynthCount() external view returns (uint); - - function availablePynths(uint index) external view returns (IPynth); - - function collateral(address account) external view returns (uint); - - function collateralisationRatio(address issuer) external view returns (uint); - - function debtBalanceOf(address issuer, bytes32 currencyKey) external view returns (uint); - - function isWaitingPeriod(bytes32 currencyKey) external view returns (bool); - - function maxIssuablePynths(address issuer) external view returns (uint maxIssuable); - - function externalTokenQuota( - address _account, - uint _additionalpUSD, - uint _additionalExToken, - bool _isIssue - ) external view returns (uint); - - function remainingIssuablePynths(address issuer) - external - view - returns ( - uint maxIssuable, - uint alreadyIssued, - uint totalSystemDebt - ); - - function maxExternalTokenStakeAmount(address _account, bytes32 _currencyKey) - external - view - returns (uint issueAmountToQuota, uint stakeAmountToQuota); - - function pynths(bytes32 currencyKey) external view returns (IPynth); - - function pynthsByAddress(address pynthAddress) external view returns (bytes32); - - function totalIssuedPynths(bytes32 currencyKey) external view returns (uint); - - function totalIssuedPynthsExcludeEtherCollateral(bytes32 currencyKey) external view returns (uint); - - function transferablePeriFinance(address account) external view returns (uint transferable); - - // Mutative Functions - function issuePynths(bytes32 _currencyKey, uint _issueAmount) external; - - function issueMaxPynths() external; - - function issuePynthsToMaxQuota(bytes32 _currencyKey) external; - - function burnPynths(bytes32 _currencyKey, uint _burnAmount) external; - - function fitToClaimable() external; - - function exit() external; - - function exchange( - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey - ) external returns (uint amountReceived); - - function exchangeOnBehalf( - address exchangeForAddress, - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey - ) external returns (uint amountReceived); - - function exchangeWithTracking( - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey, - address originator, - bytes32 trackingCode - ) external returns (uint amountReceived); - - function exchangeOnBehalfWithTracking( - address exchangeForAddress, - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey, - address originator, - bytes32 trackingCode - ) external returns (uint amountReceived); - - function exchangeWithVirtual( - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey, - bytes32 trackingCode - ) external returns (uint amountReceived, IVirtualPynth vPynth); - - function mint(address _user, uint _amount) external returns (bool); - - function inflationalMint(uint _networkDebtShare) external returns (bool); - - function settle(bytes32 currencyKey) - external - returns ( - uint reclaimed, - uint refunded, - uint numEntries - ); - - // Liquidations - function liquidateDelinquentAccount(address account, uint pusdAmount) external returns (bool); - - // Restricted Functions - - function mintSecondary(address account, uint amount) external; - - function mintSecondaryRewards(uint amount) external; - - function burnSecondary(address account, uint amount) external; -} - - -// https://docs.peri.finance/contracts/source/interfaces/ifeepool -interface IFeePool { - // Views - - // solhint-disable-next-line func-name-mixedcase - function FEE_ADDRESS() external view returns (address); - - function feesAvailable(address account) external view returns (uint, uint); - - function feePeriodDuration() external view returns (uint); - - function isFeesClaimable(address account) external view returns (bool); - - function targetThreshold() external view returns (uint); - - function totalFeesAvailable() external view returns (uint); - - function totalRewardsAvailable() external view returns (uint); - - // Mutative Functions - function claimFees() external returns (bool); - - function claimOnBehalf(address claimingForAddress) external returns (bool); - - function closeCurrentFeePeriod() external; - - // Restricted: used internally to PeriFinance - function appendAccountIssuanceRecord( - address account, - uint lockedAmount, - uint debtEntryIndex - ) external; - - function recordFeePaid(uint pUSDAmount) external; - - function setRewardsToDistribute(uint amount) external; -} - - -// https://docs.peri.finance/contracts/source/interfaces/iperiFinancestate -interface IPeriFinanceState { - // Views - function debtLedger(uint index) external view returns (uint); - - function issuanceData(address account) external view returns (uint initialDebtOwnership, uint debtEntryIndex); - - function debtLedgerLength() external view returns (uint); - - function hasIssued(address account) external view returns (bool); - - function lastDebtLedgerEntry() external view returns (uint); - - // Mutative functions - function incrementTotalIssuerCount() external; - - function decrementTotalIssuerCount() external; - - function setCurrentIssuanceData(address account, uint initialDebtOwnership) external; - - function appendDebtLedgerValue(uint value) external; - - function clearIssuanceData(address account) external; -} - - -// https://docs.peri.finance/contracts/source/interfaces/iexchanger -interface IExchanger { - // Views - function calculateAmountAfterSettlement( - address from, - bytes32 currencyKey, - uint amount, - uint refunded - ) external view returns (uint amountAfterSettlement); - - function isPynthRateInvalid(bytes32 currencyKey) external view returns (bool); - - function maxSecsLeftInWaitingPeriod(address account, bytes32 currencyKey) external view returns (uint); - - function settlementOwing(address account, bytes32 currencyKey) - external - view - returns ( - uint reclaimAmount, - uint rebateAmount, - uint numEntries - ); - - function hasWaitingPeriodOrSettlementOwing(address account, bytes32 currencyKey) external view returns (bool); - - function feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) - external - view - returns (uint exchangeFeeRate); - - function getAmountsForExchange( - uint sourceAmount, - bytes32 sourceCurrencyKey, - bytes32 destinationCurrencyKey - ) - external - view - returns ( - uint amountReceived, - uint fee, - uint exchangeFeeRate - ); - - function priceDeviationThresholdFactor() external view returns (uint); - - function waitingPeriodSecs() external view returns (uint); - - // Mutative functions - function exchange( - address from, - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey, - address destinationAddress - ) external returns (uint amountReceived); - - function exchangeOnBehalf( - address exchangeForAddress, - address from, - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey - ) external returns (uint amountReceived); - - function exchangeWithTracking( - address from, - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey, - address destinationAddress, - address originator, - bytes32 trackingCode - ) external returns (uint amountReceived); - - function exchangeOnBehalfWithTracking( - address exchangeForAddress, - address from, - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey, - address originator, - bytes32 trackingCode - ) external returns (uint amountReceived); - - function exchangeWithVirtual( - address from, - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey, - address destinationAddress, - bytes32 trackingCode - ) external returns (uint amountReceived, IVirtualPynth vPynth); - - function settle(address from, bytes32 currencyKey) - external - returns ( - uint reclaimed, - uint refunded, - uint numEntries - ); - - function setLastExchangeRateForPynth(bytes32 currencyKey, uint rate) external; - - function suspendPynthWithInvalidRate(bytes32 currencyKey) external; -} - - -// https://docs.peri.finance/contracts/source/interfaces/idelegateapprovals -interface IDelegateApprovals { - // Views - function canBurnFor(address authoriser, address delegate) external view returns (bool); - - function canIssueFor(address authoriser, address delegate) external view returns (bool); - - function canClaimFor(address authoriser, address delegate) external view returns (bool); - - function canExchangeFor(address authoriser, address delegate) external view returns (bool); - - // Mutative - function approveAllDelegatePowers(address delegate) external; - - function removeAllDelegatePowers(address delegate) external; - - function approveBurnOnBehalf(address delegate) external; - - function removeBurnOnBehalf(address delegate) external; - - function approveIssueOnBehalf(address delegate) external; - - function removeIssueOnBehalf(address delegate) external; - - function approveClaimOnBehalf(address delegate) external; - - function removeClaimOnBehalf(address delegate) external; - - function approveExchangeOnBehalf(address delegate) external; - - function removeExchangeOnBehalf(address delegate) external; -} - - -// https://docs.peri.finance/contracts/source/interfaces/iexchangerates -interface IExchangeRates { - // Structs - struct RateAndUpdatedTime { - uint216 rate; - uint40 time; - } - - struct InversePricing { - uint entryPoint; - uint upperLimit; - uint lowerLimit; - bool frozenAtUpperLimit; - bool frozenAtLowerLimit; - } - - // Views - function aggregators(bytes32 currencyKey) external view returns (address); - - function aggregatorWarningFlags() external view returns (address); - - function anyRateIsInvalid(bytes32[] calldata currencyKeys) external view returns (bool); - - function canFreezeRate(bytes32 currencyKey) external view returns (bool); - - function currentRoundForRate(bytes32 currencyKey) external view returns (uint); - - function currenciesUsingAggregator(address aggregator) external view returns (bytes32[] memory); - - function effectiveValue( - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey - ) external view returns (uint value); - - function effectiveValueAndRates( - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey - ) - external - view - returns ( - uint value, - uint sourceRate, - uint destinationRate - ); - - function effectiveValueAtRound( - bytes32 sourceCurrencyKey, - uint sourceAmount, - bytes32 destinationCurrencyKey, - uint roundIdForSrc, - uint roundIdForDest - ) external view returns (uint value); - - function getCurrentRoundId(bytes32 currencyKey) external view returns (uint); - - function getLastRoundIdBeforeElapsedSecs( - bytes32 currencyKey, - uint startingRoundId, - uint startingTimestamp, - uint timediff - ) external view returns (uint); - - function inversePricing(bytes32 currencyKey) - external - view - returns ( - uint entryPoint, - uint upperLimit, - uint lowerLimit, - bool frozenAtUpperLimit, - bool frozenAtLowerLimit - ); - - function lastRateUpdateTimes(bytes32 currencyKey) external view returns (uint256); - - function oracle() external view returns (address); - - function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time); - - function rateAndUpdatedTime(bytes32 currencyKey) external view returns (uint rate, uint time); - - function rateAndInvalid(bytes32 currencyKey) external view returns (uint rate, bool isInvalid); - - function rateForCurrency(bytes32 currencyKey) external view returns (uint); - - function rateIsFlagged(bytes32 currencyKey) external view returns (bool); - - function rateIsFrozen(bytes32 currencyKey) external view returns (bool); - - function rateIsInvalid(bytes32 currencyKey) external view returns (bool); - - function rateIsStale(bytes32 currencyKey) external view returns (bool); - - function rateStalePeriod() external view returns (uint); - - function ratesAndUpdatedTimeForCurrencyLastNRounds(bytes32 currencyKey, uint numRounds) - external - view - returns (uint[] memory rates, uint[] memory times); - - function ratesAndInvalidForCurrencies(bytes32[] calldata currencyKeys) - external - view - returns (uint[] memory rates, bool anyRateInvalid); - - function ratesForCurrencies(bytes32[] calldata currencyKeys) external view returns (uint[] memory); - - // Mutative functions - function freezeRate(bytes32 currencyKey) external; -} - - -// https://docs.peri.finance/contracts/source/interfaces/iethercollateral -interface IEtherCollateral { - // Views - function totalIssuedPynths() external view returns (uint256); - - function totalLoansCreated() external view returns (uint256); - - function totalOpenLoanCount() external view returns (uint256); - - // Mutative functions - function openLoan() external payable returns (uint256 loanID); - - function closeLoan(uint256 loanID) external; - - function liquidateUnclosedLoan(address _loanCreatorsAddress, uint256 _loanID) external; -} - - -// https://docs.peri.finance/contracts/source/interfaces/iethercollateralsusd -interface IEtherCollateralpUSD { - // Views - function totalIssuedPynths() external view returns (uint256); - - function totalLoansCreated() external view returns (uint256); - - function totalOpenLoanCount() external view returns (uint256); - - // Mutative functions - function openLoan(uint256 _loanAmount) external payable returns (uint256 loanID); - - function closeLoan(uint256 loanID) external; - - function liquidateUnclosedLoan(address _loanCreatorsAddress, uint256 _loanID) external; - - function depositCollateral(address account, uint256 loanID) external payable; - - function withdrawCollateral(uint256 loanID, uint256 withdrawAmount) external; - - function repayLoan( - address _loanCreatorsAddress, - uint256 _loanID, - uint256 _repayAmount - ) external; -} - - -// https://docs.peri.finance/contracts/source/interfaces/ihasbalance -interface IHasBalance { - // Views - function balanceOf(address account) external view returns (uint); -} - - -// https://docs.peri.finance/contracts/source/interfaces/ierc20 -interface IERC20 { - // ERC20 Optional Views - function name() external view returns (string memory); - - function symbol() external view returns (string memory); - - function decimals() external view returns (uint8); - - // Views - function totalSupply() external view returns (uint); - - function balanceOf(address owner) external view returns (uint); - - function allowance(address owner, address spender) external view returns (uint); - - // Mutative functions - function transfer(address to, uint value) external returns (bool); - - function approve(address spender, uint value) external returns (bool); - - function transferFrom( - address from, - address to, - uint value - ) external returns (bool); - - // Events - event Transfer(address indexed from, address indexed to, uint value); - - event Approval(address indexed owner, address indexed spender, uint value); -} - - -// https://docs.peri.finance/contracts/source/interfaces/iliquidations -interface ILiquidations { - // Views - function isOpenForLiquidation(address account) external view returns (bool); - - function getLiquidationDeadlineForAccount(address account) external view returns (uint); - - function isLiquidationDeadlinePassed(address account) external view returns (bool); - - function liquidationDelay() external view returns (uint); - - function liquidationRatio() external view returns (uint); - - function liquidationPenalty() external view returns (uint); - - function calculateAmountToFixCollateral(uint debtBalance, uint collateral) external view returns (uint); - - // Mutative Functions - function flagAccountForLiquidation(address account) external; - - // Restricted: used internally to PeriFinance - function removeAccountInLiquidation(address account) external; - - function checkAndRemoveAccountInLiquidation(address account) external; -} - - -interface ICollateralManager { - // Manager information - function hasCollateral(address collateral) external view returns (bool); - - function isPynthManaged(bytes32 currencyKey) external view returns (bool); - - // State information - function long(bytes32 pynth) external view returns (uint amount); - - function short(bytes32 pynth) external view returns (uint amount); - - function totalLong() external view returns (uint pusdValue, bool anyRateIsInvalid); - - function totalShort() external view returns (uint pusdValue, bool anyRateIsInvalid); - - function getBorrowRate() external view returns (uint borrowRate, bool anyRateIsInvalid); - - function getShortRate(bytes32 pynth) external view returns (uint shortRate, bool rateIsInvalid); - - function getRatesAndTime(uint index) - external - view - returns ( - uint entryRate, - uint lastRate, - uint lastUpdated, - uint newIndex - ); - - function getShortRatesAndTime(bytes32 currency, uint index) - external - view - returns ( - uint entryRate, - uint lastRate, - uint lastUpdated, - uint newIndex - ); - - function exceedsDebtLimit(uint amount, bytes32 currency) external view returns (bool canIssue, bool anyRateIsInvalid); - - function arePynthsAndCurrenciesSet(bytes32[] calldata requiredPynthNamesInResolver, bytes32[] calldata pynthKeys) - external - view - returns (bool); - - function areShortablePynthsSet(bytes32[] calldata requiredPynthNamesInResolver, bytes32[] calldata pynthKeys) - external - view - returns (bool); - - // Loans - function getNewLoanId() external returns (uint id); - - // Manager mutative - function addCollaterals(address[] calldata collaterals) external; - - function removeCollaterals(address[] calldata collaterals) external; - - function addPynths(bytes32[] calldata pynthNamesInResolver, bytes32[] calldata pynthKeys) external; - - function removePynths(bytes32[] calldata pynths, bytes32[] calldata pynthKeys) external; - - function addShortablePynths(bytes32[2][] calldata requiredPynthAndInverseNamesInResolver, bytes32[] calldata pynthKeys) - external; - - function removeShortablePynths(bytes32[] calldata pynths) external; - - // State mutative - function updateBorrowRates(uint rate) external; - - function updateShortRates(bytes32 currency, uint rate) external; - - function incrementLongs(bytes32 pynth, uint amount) external; - - function decrementLongs(bytes32 pynth, uint amount) external; - - function incrementShorts(bytes32 pynth, uint amount) external; - - function decrementShorts(bytes32 pynth, uint amount) external; -} - - -contract IExternalTokenStakeManager { - function stake( - address _staker, - uint _amount, - bytes32 _targetCurrency, - bytes32 _inputCurrency - ) external; - - function unstake( - address _unstaker, - uint _amount, - bytes32 _targetCurrency, - bytes32 _inputCurrency - ) external; - - function unstakeMultipleTokens( - address _unstaker, - uint _amount, - bytes32 _inputCurrency - ) external; - - function getTokenList() external view returns (bytes32[] memory); - - function getTokenAddress(bytes32 _currencyKey) external view returns (address); - - function getTokenDecimals(bytes32 _currencyKey) external view returns (uint8); - - function getTokenActivation(bytes32 _currencyKey) external view returns (bool); - - function getCurrencyKeyOrder() external view returns (bytes32[] memory); - - function combinedStakedAmountOf(address _user, bytes32 _unitCurrency) external view returns (uint); - - function stakedAmountOf( - address _user, - bytes32 _currencyKey, - bytes32 _unitCurrency - ) external view returns (uint); -} - - -// Inheritance - - -// Libraries - - -// Internal references - - -interface IRewardEscrowV2 { - // Views - function balanceOf(address account) external view returns (uint); -} - -interface IIssuerInternalDebtCache { - function updateCachedPynthDebtWithRate(bytes32 currencyKey, uint currencyRate) external; - - function updateCachedPynthDebtsWithRates(bytes32[] calldata currencyKeys, uint[] calldata currencyRates) external; - - function updateDebtCacheValidity(bool currentlyInvalid) external; - - function cacheInfo() - external - view - returns ( - uint cachedDebt, - uint timestamp, - bool isInvalid, - bool isStale - ); -} - -// https://docs.peri.finance/contracts/source/contracts/issuer -contract Issuer is Owned, MixinSystemSettings, IIssuer { - using SafeMath for uint; - using SafeDecimalMath for uint; - - // Available Pynths which can be used with the system - IPynth[] public availablePynths; - mapping(bytes32 => IPynth) public pynths; - mapping(address => bytes32) public pynthsByAddress; - - /* ========== ENCODED NAMES ========== */ - - bytes32 internal constant pUSD = "pUSD"; - bytes32 internal constant pETH = "pETH"; - bytes32 internal constant PERI = "PERI"; - bytes32 internal constant USDC = "USDC"; - - // Flexible storage names - - bytes32 public constant CONTRACT_NAME = "Issuer"; - bytes32 internal constant LAST_ISSUE_EVENT = "lastIssueEvent"; - - /* ========== ADDRESS RESOLVER CONFIGURATION ========== */ - - bytes32 private constant CONTRACT_PERIFINANCE = "PeriFinance"; - bytes32 private constant CONTRACT_EXCHANGER = "Exchanger"; - bytes32 private constant CONTRACT_EXRATES = "ExchangeRates"; - bytes32 private constant CONTRACT_PERIFINANCESTATE = "PeriFinanceState"; - bytes32 private constant CONTRACT_FEEPOOL = "FeePool"; - bytes32 private constant CONTRACT_DELEGATEAPPROVALS = "DelegateApprovals"; - bytes32 private constant CONTRACT_ETHERCOLLATERAL = "EtherCollateral"; - bytes32 private constant CONTRACT_ETHERCOLLATERAL_PUSD = "EtherCollateralpUSD"; - bytes32 private constant CONTRACT_COLLATERALMANAGER = "CollateralManager"; - bytes32 private constant CONTRACT_REWARDESCROW_V2 = "RewardEscrowV2"; - bytes32 private constant CONTRACT_PERIFINANCEESCROW = "PeriFinanceEscrow"; - bytes32 private constant CONTRACT_LIQUIDATIONS = "Liquidations"; - bytes32 private constant CONTRACT_DEBTCACHE = "DebtCache"; - bytes32 private constant CONTRACT_EXTOKENSTAKEMANAGER = "ExternalTokenStakeManager"; - - constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {} - - /* ========== VIEWS ========== */ - function resolverAddressesRequired() public view returns (bytes32[] memory addresses) { - bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired(); - bytes32[] memory newAddresses = new bytes32[](14); - newAddresses[0] = CONTRACT_PERIFINANCE; - newAddresses[1] = CONTRACT_EXCHANGER; - newAddresses[2] = CONTRACT_EXRATES; - newAddresses[3] = CONTRACT_PERIFINANCESTATE; - newAddresses[4] = CONTRACT_FEEPOOL; - newAddresses[5] = CONTRACT_DELEGATEAPPROVALS; - newAddresses[6] = CONTRACT_ETHERCOLLATERAL; - newAddresses[7] = CONTRACT_ETHERCOLLATERAL_PUSD; - newAddresses[8] = CONTRACT_REWARDESCROW_V2; - newAddresses[9] = CONTRACT_PERIFINANCEESCROW; - newAddresses[10] = CONTRACT_LIQUIDATIONS; - newAddresses[11] = CONTRACT_DEBTCACHE; - newAddresses[12] = CONTRACT_COLLATERALMANAGER; - newAddresses[13] = CONTRACT_EXTOKENSTAKEMANAGER; - return combineArrays(existingAddresses, newAddresses); - } - - function periFinance() internal view returns (IPeriFinance) { - return IPeriFinance(requireAndGetAddress(CONTRACT_PERIFINANCE)); - } - - function exTokenStakeManager() internal view returns (IExternalTokenStakeManager) { - return IExternalTokenStakeManager(requireAndGetAddress(CONTRACT_EXTOKENSTAKEMANAGER)); - } - - function exchanger() internal view returns (IExchanger) { - return IExchanger(requireAndGetAddress(CONTRACT_EXCHANGER)); - } - - function exchangeRates() internal view returns (IExchangeRates) { - return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES)); - } - - function periFinanceState() internal view returns (IPeriFinanceState) { - return IPeriFinanceState(requireAndGetAddress(CONTRACT_PERIFINANCESTATE)); - } - - function feePool() internal view returns (IFeePool) { - return IFeePool(requireAndGetAddress(CONTRACT_FEEPOOL)); - } - - function liquidations() internal view returns (ILiquidations) { - return ILiquidations(requireAndGetAddress(CONTRACT_LIQUIDATIONS)); - } - - function delegateApprovals() internal view returns (IDelegateApprovals) { - return IDelegateApprovals(requireAndGetAddress(CONTRACT_DELEGATEAPPROVALS)); - } - - function etherCollateral() internal view returns (IEtherCollateral) { - return IEtherCollateral(requireAndGetAddress(CONTRACT_ETHERCOLLATERAL)); - } - - function etherCollateralpUSD() internal view returns (IEtherCollateralpUSD) { - return IEtherCollateralpUSD(requireAndGetAddress(CONTRACT_ETHERCOLLATERAL_PUSD)); - } - - function collateralManager() internal view returns (ICollateralManager) { - return ICollateralManager(requireAndGetAddress(CONTRACT_COLLATERALMANAGER)); - } - - function rewardEscrowV2() internal view returns (IRewardEscrowV2) { - return IRewardEscrowV2(requireAndGetAddress(CONTRACT_REWARDESCROW_V2)); - } - - function periFinanceEscrow() internal view returns (IHasBalance) { - return IHasBalance(requireAndGetAddress(CONTRACT_PERIFINANCEESCROW)); - } - - function debtCache() internal view returns (IIssuerInternalDebtCache) { - return IIssuerInternalDebtCache(requireAndGetAddress(CONTRACT_DEBTCACHE)); - } - - function issuanceRatio() external view returns (uint) { - return getIssuanceRatio(); - } - - function externalTokenLimit() external view returns (uint) { - return getExternalTokenQuota(); - } - - function _availableCurrencyKeysWithOptionalPERI(bool withPERI) internal view returns (bytes32[] memory) { - bytes32[] memory currencyKeys = new bytes32[](availablePynths.length + (withPERI ? 1 : 0)); - - for (uint i = 0; i < availablePynths.length; i++) { - currencyKeys[i] = pynthsByAddress[address(availablePynths[i])]; - } - - if (withPERI) { - currencyKeys[availablePynths.length] = PERI; - } - - return currencyKeys; - } - - function _totalIssuedPynths(bytes32 currencyKey, bool excludeCollateral) - internal - view - returns (uint totalIssued, bool anyRateIsInvalid) - { - (uint debt, , bool cacheIsInvalid, bool cacheIsStale) = debtCache().cacheInfo(); - anyRateIsInvalid = cacheIsInvalid || cacheIsStale; - - IExchangeRates exRates = exchangeRates(); - - // Add total issued pynths from non peri collateral back into the total if not excluded - if (!excludeCollateral) { - // Get the pUSD equivalent amount of all the MC issued pynths. - (uint nonPeriDebt, bool invalid) = collateralManager().totalLong(); - debt = debt.add(nonPeriDebt); - anyRateIsInvalid = anyRateIsInvalid || invalid; - - // Now add the ether collateral stuff as we are still supporting it. - debt = debt.add(etherCollateralpUSD().totalIssuedPynths()); - - // Add ether collateral pETH - (uint ethRate, bool ethRateInvalid) = exRates.rateAndInvalid(pETH); - uint ethIssuedDebt = etherCollateral().totalIssuedPynths().multiplyDecimalRound(ethRate); - debt = debt.add(ethIssuedDebt); - anyRateIsInvalid = anyRateIsInvalid || ethRateInvalid; - } - - if (currencyKey == pUSD) { - return (debt, anyRateIsInvalid); - } - - (uint currencyRate, bool currencyRateInvalid) = exRates.rateAndInvalid(currencyKey); - return (debt.divideDecimalRound(currencyRate), anyRateIsInvalid || currencyRateInvalid); - } - - function _debtBalanceOfAndTotalDebt(address _issuer, bytes32 currencyKey) - internal - view - returns ( - uint debtBalance, - uint totalSystemValue, - bool anyRateIsInvalid - ) - { - IPeriFinanceState state = periFinanceState(); - - // What was their initial debt ownership? - (uint initialDebtOwnership, uint debtEntryIndex) = state.issuanceData(_issuer); - - // What's the total value of the system excluding ETH backed pynths in their requested currency? - (totalSystemValue, anyRateIsInvalid) = _totalIssuedPynths(currencyKey, true); - - // If it's zero, they haven't issued, and they have no debt. - // Note: it's more gas intensive to put this check here rather than before _totalIssuedPynths - // if they have 0 PERI, but it's a necessary trade-off - if (initialDebtOwnership == 0) return (0, totalSystemValue, anyRateIsInvalid); - - // Figure out the global debt percentage delta from when they entered the system. - // This is a high precision integer of 27 (1e27) decimals. - uint _debtLedgerLength = state.debtLedgerLength(); - uint systemDebt = state.debtLedger(debtEntryIndex); - uint currentDebtOwnership; - if (_debtLedgerLength == 0 || systemDebt == 0) { - currentDebtOwnership = 0; - } else { - currentDebtOwnership = state - .lastDebtLedgerEntry() - .divideDecimalRoundPrecise(systemDebt) - .multiplyDecimalRoundPrecise(initialDebtOwnership); - } - - // Their debt balance is their portion of the total system value. - uint highPrecisionBalance = - totalSystemValue.decimalToPreciseDecimal().multiplyDecimalRoundPrecise(currentDebtOwnership); - - // Convert back into 18 decimals (1e18) - debtBalance = highPrecisionBalance.preciseDecimalToDecimal(); - } - - function _canBurnPynths(address account) internal view returns (bool) { - return now >= _lastIssueEvent(account).add(getMinimumStakeTime()); - } - - function _lastIssueEvent(address account) internal view returns (uint) { - // Get the timestamp of the last issue this account made - return flexibleStorage().getUIntValue(CONTRACT_NAME, keccak256(abi.encodePacked(LAST_ISSUE_EVENT, account))); - } - - function _remainingIssuablePynths(address _issuer) - internal - view - returns ( - uint maxIssuable, - uint alreadyIssued, - uint totalSystemDebt, - bool anyRateIsInvalid - ) - { - (alreadyIssued, totalSystemDebt, anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_issuer, pUSD); - (uint issuable, bool isInvalid) = _maxIssuablePynths(_issuer); - maxIssuable = issuable; - anyRateIsInvalid = anyRateIsInvalid || isInvalid; - - if (alreadyIssued >= maxIssuable) { - maxIssuable = 0; - } else { - maxIssuable = maxIssuable.sub(alreadyIssued); - } - } - - function _periToUSD(uint amount, uint periRate) internal pure returns (uint) { - return amount.multiplyDecimalRound(periRate); - } - - function _usdToPeri(uint amount, uint periRate) internal pure returns (uint) { - return amount.divideDecimalRound(periRate); - } - - function _maxIssuablePynths(address _issuer) internal view returns (uint, bool) { - // What is the value of their PERI balance in pUSD - (uint periRate, bool periRateIsInvalid) = exchangeRates().rateAndInvalid(PERI); - uint periCollateral = _periToUSD(_collateral(_issuer), periRate); - - uint externalTokenStaked = exTokenStakeManager().combinedStakedAmountOf(_issuer, pUSD); - - uint destinationValue = periCollateral.add(externalTokenStaked); - - // They're allowed to issue up to issuanceRatio of that value - return (destinationValue.multiplyDecimal(getIssuanceRatio()), periRateIsInvalid); - } - - function _collateralisationRatio(address _issuer) internal view returns (uint, bool) { - uint totalOwnedPeriFinance = _collateral(_issuer); - uint externalTokenStaked = exTokenStakeManager().combinedStakedAmountOf(_issuer, PERI); - - (uint debtBalance, , bool anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_issuer, PERI); - - // it's more gas intensive to put this check here if they have 0 PERI, but it complies with the interface - if (totalOwnedPeriFinance == 0 && externalTokenStaked == 0) return (0, anyRateIsInvalid); - - uint totalOwned = totalOwnedPeriFinance.add(externalTokenStaked); - - return (debtBalance.divideDecimal(totalOwned), anyRateIsInvalid); - } - - function _collateral(address account) internal view returns (uint) { - uint balance = IERC20(address(periFinance())).balanceOf(account); - - if (address(periFinanceEscrow()) != address(0)) { - balance = balance.add(periFinanceEscrow().balanceOf(account)); - } - - if (address(rewardEscrowV2()) != address(0)) { - balance = balance.add(rewardEscrowV2().balanceOf(account)); - } - - return balance; - } - - /** - * @notice It calculates the quota of user's staked amount to the debt. - * If parameters are not 0, it estimates the quota assuming those value is applied to current status. - * - * @param _account account - * @param _debtBalance Debt balance to estimate [USD] - * @param _additionalpUSD The pUSD value to be applied for estimation [USD] - * @param _additionalExToken The external token stake amount to be applied for estimation [USD] - * @param _isIssue If true, it is considered issueing/staking estimation. - */ - function _externalTokenQuota( - address _account, - uint _debtBalance, - uint _additionalpUSD, - uint _additionalExToken, - bool _isIssue - ) internal view returns (uint) { - uint combinedStakedAmount = exTokenStakeManager().combinedStakedAmountOf(_account, pUSD); - - if (_debtBalance == 0 || combinedStakedAmount == 0) { - return 0; - } - - if (_isIssue) { - _debtBalance = _debtBalance.add(_additionalpUSD); - combinedStakedAmount = combinedStakedAmount.add(_additionalExToken); - } else { - _debtBalance = _debtBalance.sub(_additionalpUSD); - combinedStakedAmount = combinedStakedAmount.sub(_additionalExToken); - } - - return combinedStakedAmount.divideDecimalRound(_debtBalance.divideDecimalRound(getIssuanceRatio())); - } - - function _amountsToFitClaimable( - uint _currentDebt, - uint _stakedExTokenAmount, - uint _periCollateral - ) internal view returns (uint burnAmount, uint exTokenAmountToUnstake) { - uint targetRatio = getIssuanceRatio(); - uint exTokenQuota = getExternalTokenQuota(); - - uint initialCRatio = _currentDebt.divideDecimal(_stakedExTokenAmount.add(_periCollateral)); - // it doesn't satisfy target c-ratio - if (initialCRatio > targetRatio) { - uint maxAllowedExTokenStakeAmountByPeriCollateral = - _periCollateral.multiplyDecimal(exTokenQuota.divideDecimal(SafeDecimalMath.unit().sub(exTokenQuota))); - exTokenAmountToUnstake = _stakedExTokenAmount > maxAllowedExTokenStakeAmountByPeriCollateral - ? _stakedExTokenAmount.sub(maxAllowedExTokenStakeAmountByPeriCollateral) - : 0; - burnAmount = _currentDebt.sub( - _periCollateral.add(_stakedExTokenAmount).sub(exTokenAmountToUnstake).multiplyDecimal(targetRatio) - ); - - // it satisfies target c-ratio but violates external token quota - } else { - uint currentExTokenQuota = _stakedExTokenAmount.multiplyDecimal(targetRatio).divideDecimal(_currentDebt); - require(currentExTokenQuota > exTokenQuota, "Account is already claimable"); - - burnAmount = (_stakedExTokenAmount.multiplyDecimal(targetRatio).sub(_currentDebt.multiplyDecimal(exTokenQuota))) - .divideDecimal(SafeDecimalMath.unit().sub(exTokenQuota)); - exTokenAmountToUnstake = burnAmount.divideDecimal(targetRatio); - } - } - - /** - * @notice It calculates maximum issue/stake(external token) amount to meet external token quota limit. - * - * @param _from target address - * @param _debtBalance current debt balance[pUSD] - * @param _stakedAmount currently target address's external token staked amount[pUSD] - * @param _currencyKey currency key of external token to stake - */ - function _maxExternalTokenStakeAmount( - address _from, - uint _debtBalance, - uint _stakedAmount, - bytes32 _currencyKey - ) internal view returns (uint issueAmount, uint stakeAmount) { - uint targetRatio = getIssuanceRatio(); - uint quotaLimit = getExternalTokenQuota(); - - uint maxAllowedStakingAmount = _debtBalance.multiplyDecimal(quotaLimit).divideDecimal(targetRatio); - if (_stakedAmount >= maxAllowedStakingAmount) { - return (0, 0); - } - - stakeAmount = ((maxAllowedStakingAmount).sub(_stakedAmount)).divideDecimal(SafeDecimalMath.unit().sub(quotaLimit)); - - uint balance = IERC20(exTokenStakeManager().getTokenAddress(_currencyKey)).balanceOf(_from); - stakeAmount = balance < stakeAmount ? balance : stakeAmount; - issueAmount = stakeAmount.multiplyDecimal(targetRatio); - } - - function minimumStakeTime() external view returns (uint) { - return getMinimumStakeTime(); - } - - function canBurnPynths(address account) external view returns (bool) { - return _canBurnPynths(account); - } - - function availableCurrencyKeys() external view returns (bytes32[] memory) { - return _availableCurrencyKeysWithOptionalPERI(false); - } - - function availablePynthCount() external view returns (uint) { - return availablePynths.length; - } - - function anyPynthOrPERIRateIsInvalid() external view returns (bool anyRateInvalid) { - (, anyRateInvalid) = exchangeRates().ratesAndInvalidForCurrencies(_availableCurrencyKeysWithOptionalPERI(true)); - } - - function totalIssuedPynths(bytes32 currencyKey, bool excludeEtherCollateral) external view returns (uint totalIssued) { - (totalIssued, ) = _totalIssuedPynths(currencyKey, excludeEtherCollateral); - } - - function lastIssueEvent(address account) external view returns (uint) { - return _lastIssueEvent(account); - } - - function collateralisationRatio(address _issuer) external view returns (uint cratio) { - (cratio, ) = _collateralisationRatio(_issuer); - } - - function collateralisationRatioAndAnyRatesInvalid(address _issuer) - external - view - returns (uint cratio, bool anyRateIsInvalid) - { - return _collateralisationRatio(_issuer); - } - - function collateral(address account) external view returns (uint) { - return _collateral(account); - } - - function debtBalanceOf(address _issuer, bytes32 currencyKey) external view returns (uint debtBalance) { - IPeriFinanceState state = periFinanceState(); - - // What was their initial debt ownership? - (uint initialDebtOwnership, ) = state.issuanceData(_issuer); - - // If it's zero, they haven't issued, and they have no debt. - if (initialDebtOwnership == 0) return 0; - - (debtBalance, , ) = _debtBalanceOfAndTotalDebt(_issuer, currencyKey); - } - - function remainingIssuablePynths(address _issuer) - external - view - returns ( - uint maxIssuable, - uint alreadyIssued, - uint totalSystemDebt - ) - { - (maxIssuable, alreadyIssued, totalSystemDebt, ) = _remainingIssuablePynths(_issuer); - } - - function maxIssuablePynths(address _issuer) external view returns (uint) { - (uint maxIssuable, ) = _maxIssuablePynths(_issuer); - return maxIssuable; - } - - function externalTokenQuota( - address _account, - uint _additionalpUSD, - uint _additionalExToken, - bool _isIssue - ) external view returns (uint) { - (uint debtBalance, , bool anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_account, pUSD); - - _requireRatesNotInvalid(anyRateIsInvalid); - - uint estimatedQuota = _externalTokenQuota(_account, debtBalance, _additionalpUSD, _additionalExToken, _isIssue); - - return estimatedQuota; - } - - function maxExternalTokenStakeAmount(address _account, bytes32 _currencyKey) - external - view - returns (uint issueAmountToQuota, uint stakeAmountToQuota) - { - (uint debtBalance, , ) = _debtBalanceOfAndTotalDebt(_account, pUSD); - - uint combinedStakedAmount = exTokenStakeManager().combinedStakedAmountOf(_account, pUSD); - - (issueAmountToQuota, stakeAmountToQuota) = _maxExternalTokenStakeAmount( - _account, - debtBalance, - combinedStakedAmount, - _currencyKey - ); - } - - function transferablePeriFinanceAndAnyRateIsInvalid(address account, uint balance) - external - view - returns (uint transferable, bool anyRateIsInvalid) - { - // How many PERI do they have, excluding escrow? - // Note: We're excluding escrow here because we're interested in their transferable amount - // and escrowed PERI are not transferable. - - // How many of those will be locked by the amount they've issued? - // Assuming issuance ratio is 20%, then issuing 20 PERI of value would require - // 100 PERI to be locked in their wallet to maintain their collateralisation ratio - // The locked periFinance value can exceed their balance. - (uint debtBalance, , bool rateIsInvalid) = _debtBalanceOfAndTotalDebt(account, PERI); - - uint debtAppliedIssuanceRatio = debtBalance.divideDecimalRound(getIssuanceRatio()); - - uint externalTokenStaked = exTokenStakeManager().combinedStakedAmountOf(account, PERI); - - // If external token staked balance is larger than required collateral amount for current debt, - // no PERI would be locked. (But it violates external token staking quota rule) - uint lockedPeriFinanceValue = - debtAppliedIssuanceRatio > externalTokenStaked ? debtAppliedIssuanceRatio.sub(externalTokenStaked) : 0; - - // If we exceed the balance, no PERI are transferable, otherwise the difference is. - if (lockedPeriFinanceValue >= balance) { - transferable = 0; - } else { - transferable = balance.sub(lockedPeriFinanceValue); - } - - anyRateIsInvalid = rateIsInvalid; - } - - function getPynths(bytes32[] calldata currencyKeys) external view returns (IPynth[] memory) { - uint numKeys = currencyKeys.length; - IPynth[] memory addresses = new IPynth[](numKeys); - - for (uint i = 0; i < numKeys; i++) { - addresses[i] = pynths[currencyKeys[i]]; - } - - return addresses; - } - - /* ========== MUTATIVE FUNCTIONS ========== */ - - function _addPynth(IPynth pynth) internal { - bytes32 currencyKey = pynth.currencyKey(); - require(pynths[currencyKey] == IPynth(0), "Pynth exists"); - require(pynthsByAddress[address(pynth)] == bytes32(0), "Pynth address already exists"); - - availablePynths.push(pynth); - pynths[currencyKey] = pynth; - pynthsByAddress[address(pynth)] = currencyKey; - - emit PynthAdded(currencyKey, address(pynth)); - } - - function addPynth(IPynth pynth) external onlyOwner { - _addPynth(pynth); - // Invalidate the cache to force a snapshot to be recomputed. If a pynth were to be added - // back to the system and it still somehow had cached debt, this would force the value to be - // updated. - debtCache().updateDebtCacheValidity(true); - } - - function addPynths(IPynth[] calldata pynthsToAdd) external onlyOwner { - uint numPynths = pynthsToAdd.length; - for (uint i = 0; i < numPynths; i++) { - _addPynth(pynthsToAdd[i]); - } - - // Invalidate the cache to force a snapshot to be recomputed. - debtCache().updateDebtCacheValidity(true); - } - - function _removePynth(bytes32 currencyKey) internal { - address pynthToRemove = address(pynths[currencyKey]); - require(pynthToRemove != address(0), "Pynth does not exist"); - require(IERC20(pynthToRemove).totalSupply() == 0, "Pynth supply exists"); - require(currencyKey != pUSD, "Cannot remove pynth"); - - // Remove the pynth from the availablePynths array. - for (uint i = 0; i < availablePynths.length; i++) { - if (address(availablePynths[i]) == pynthToRemove) { - delete availablePynths[i]; - - // Copy the last pynth into the place of the one we just deleted - // If there's only one pynth, this is pynths[0] = pynths[0]. - // If we're deleting the last one, it's also a NOOP in the same way. - availablePynths[i] = availablePynths[availablePynths.length - 1]; - - // Decrease the size of the array by one. - availablePynths.length--; - - break; - } - } - - // And remove it from the pynths mapping - delete pynthsByAddress[pynthToRemove]; - delete pynths[currencyKey]; - - emit PynthRemoved(currencyKey, pynthToRemove); - } - - function removePynth(bytes32 currencyKey) external onlyOwner { - // Remove its contribution from the debt pool snapshot, and - // invalidate the cache to force a new snapshot. - IIssuerInternalDebtCache cache = debtCache(); - cache.updateCachedPynthDebtWithRate(currencyKey, 0); - cache.updateDebtCacheValidity(true); - - _removePynth(currencyKey); - } - - function removePynths(bytes32[] calldata currencyKeys) external onlyOwner { - uint numKeys = currencyKeys.length; - - // Remove their contributions from the debt pool snapshot, and - // invalidate the cache to force a new snapshot. - IIssuerInternalDebtCache cache = debtCache(); - uint[] memory zeroRates = new uint[](numKeys); - cache.updateCachedPynthDebtsWithRates(currencyKeys, zeroRates); - cache.updateDebtCacheValidity(true); - - for (uint i = 0; i < numKeys; i++) { - _removePynth(currencyKeys[i]); - } - } - - function issuePynths( - address _issuer, - bytes32 _currencyKey, - uint _issueAmount - ) external onlyPeriFinance { - _requireCurrencyKeyIsNotpUSD(_currencyKey); - - if (_currencyKey != PERI) { - uint amountToStake = _issueAmount.divideDecimalRound(getIssuanceRatio()); - - (uint initialDebtOwnership, ) = periFinanceState().issuanceData(_issuer); - // Condition of policy, user must have any amount of PERI locked before staking external token. - require(initialDebtOwnership > 0, "User does not have any debt yet"); - - exTokenStakeManager().stake(_issuer, amountToStake, _currencyKey, pUSD); - } - - (uint maxIssuable, uint existingDebt, uint totalSystemDebt, bool anyRateIsInvalid) = - _remainingIssuablePynths(_issuer); - _requireRatesNotInvalid(anyRateIsInvalid); - - uint afterDebtBalance = _issuePynths(_issuer, _issueAmount, maxIssuable, existingDebt, totalSystemDebt, false); - - // For preventing additional gas consumption by calculating debt twice, the quota checker is placed here. - _requireNotExceedsQuotaLimit(_issuer, afterDebtBalance, 0, 0, true); - } - - function issueMaxPynths(address _issuer) external onlyPeriFinance { - (uint maxIssuable, uint existingDebt, uint totalSystemDebt, bool anyRateIsInvalid) = - _remainingIssuablePynths(_issuer); - _requireRatesNotInvalid(anyRateIsInvalid); - - _issuePynths(_issuer, 0, maxIssuable, existingDebt, totalSystemDebt, true); - } - - function issuePynthsToMaxQuota(address _issuer, bytes32 _currencyKey) external onlyPeriFinance { - _requireCurrencyKeyIsNotpUSD(_currencyKey); - require(_currencyKey != PERI, "Only external token allowed to stake"); - - (uint maxIssuable, uint existingDebt, uint totalSystemDebt, bool anyRateIsInvalid) = - _remainingIssuablePynths(_issuer); - _requireRatesNotInvalid(anyRateIsInvalid); - require(existingDebt > 0, "User does not have any debt yet"); - - uint combinedStakedAmount = exTokenStakeManager().combinedStakedAmountOf(_issuer, pUSD); - (uint issueAmountToQuota, uint stakeAmountToQuota) = - _maxExternalTokenStakeAmount(_issuer, existingDebt, combinedStakedAmount, _currencyKey); - - require(issueAmountToQuota > 0 && stakeAmountToQuota > 0, "No available external token staking amount"); - - exTokenStakeManager().stake(_issuer, stakeAmountToQuota, _currencyKey, pUSD); - - // maxIssuable should be increased for increased collateral - maxIssuable = maxIssuable.add(issueAmountToQuota); - - uint afterDebtBalance = _issuePynths(_issuer, issueAmountToQuota, maxIssuable, existingDebt, totalSystemDebt, false); - - // For preventing additional gas consumption by calculating debt twice, the quota checker is placed here. - _requireNotExceedsQuotaLimit(_issuer, afterDebtBalance, 0, 0, true); - } - - function burnPynths( - address _from, - bytes32 _currencyKey, - uint _burnAmount - ) external onlyPeriFinance { - _requireCurrencyKeyIsNotpUSD(_currencyKey); - - uint remainingDebt = _voluntaryBurnPynths(_from, _burnAmount, false, false); - - if (_currencyKey == PERI) { - _requireNotExceedsQuotaLimit(_from, remainingDebt, 0, 0, false); - } - - if (_currencyKey != PERI) { - exTokenStakeManager().unstake(_from, _burnAmount.divideDecimalRound(getIssuanceRatio()), _currencyKey, pUSD); - } - } - - function fitToClaimable(address _from) external onlyPeriFinance { - (uint debtBalance, , bool anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_from, pUSD); - uint combinedStakedAmount = exTokenStakeManager().combinedStakedAmountOf(_from, pUSD); - - (uint periRate, bool isPeriInvalid) = exchangeRates().rateAndInvalid(PERI); - uint periCollateralToUSD = _periToUSD(_collateral(_from), periRate); - - _requireRatesNotInvalid(anyRateIsInvalid || isPeriInvalid); - - (uint burnAmount, uint amountToUnstake) = - _amountsToFitClaimable(debtBalance, combinedStakedAmount, periCollateralToUSD); - - _voluntaryBurnPynths(_from, burnAmount, true, false); - - exTokenStakeManager().unstakeMultipleTokens(_from, amountToUnstake, pUSD); - } - - function exit(address _from) external onlyPeriFinance { - _voluntaryBurnPynths(_from, 0, true, true); - - bytes32[] memory tokenList = exTokenStakeManager().getTokenList(); - for (uint i = 0; i < tokenList.length; i++) { - uint stakedAmount = exTokenStakeManager().stakedAmountOf(_from, tokenList[i], tokenList[i]); - - if (stakedAmount == 0) { - continue; - } - - exTokenStakeManager().unstake(_from, stakedAmount, tokenList[i], tokenList[i]); - } - } - - function liquidateDelinquentAccount( - address account, - uint pusdAmount, - address liquidator - ) external onlyPeriFinance returns (uint totalRedeemed, uint amountToLiquidate) { - // Ensure waitingPeriod and pUSD balance is settled as burning impacts the size of debt pool - require(!exchanger().hasWaitingPeriodOrSettlementOwing(liquidator, pUSD), "pUSD needs to be settled"); - - // Check account is liquidation open - require(liquidations().isOpenForLiquidation(account), "Account not open for liquidation"); - - // require liquidator has enough pUSD - require(IERC20(address(pynths[pUSD])).balanceOf(liquidator) >= pusdAmount, "Not enough pUSD"); - - uint liquidationPenalty = liquidations().liquidationPenalty(); - - // What is their debt in pUSD? - (uint debtBalance, uint totalDebtIssued, bool anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(account, pUSD); - (uint periRate, bool periRateInvalid) = exchangeRates().rateAndInvalid(PERI); - _requireRatesNotInvalid(anyRateIsInvalid || periRateInvalid); - - uint collateralForAccount = _collateral(account); - uint amountToFixRatio = - liquidations().calculateAmountToFixCollateral(debtBalance, _periToUSD(collateralForAccount, periRate)); - - // Cap amount to liquidate to repair collateral ratio based on issuance ratio - amountToLiquidate = amountToFixRatio < pusdAmount ? amountToFixRatio : pusdAmount; - - // what's the equivalent amount of peri for the amountToLiquidate? - uint periRedeemed = _usdToPeri(amountToLiquidate, periRate); - - // Add penalty - totalRedeemed = periRedeemed.multiplyDecimal(SafeDecimalMath.unit().add(liquidationPenalty)); - - // if total PERI to redeem is greater than account's collateral - // account is under collateralised, liquidate all collateral and reduce pUSD to burn - if (totalRedeemed > collateralForAccount) { - // set totalRedeemed to all transferable collateral - totalRedeemed = collateralForAccount; - - // whats the equivalent pUSD to burn for all collateral less penalty - amountToLiquidate = _periToUSD( - collateralForAccount.divideDecimal(SafeDecimalMath.unit().add(liquidationPenalty)), - periRate - ); - } - - // burn pUSD from messageSender (liquidator) and reduce account's debt - _burnPynths(account, liquidator, amountToLiquidate, debtBalance, totalDebtIssued); - - // Remove liquidation flag if amount liquidated fixes ratio - if (amountToLiquidate == amountToFixRatio) { - // Remove liquidation - liquidations().removeAccountInLiquidation(account); - } - } - - /* ========== INTERNAL FUNCTIONS ========== */ - - function _requireRatesNotInvalid(bool anyRateIsInvalid) internal pure { - require(!anyRateIsInvalid, "A pynth or PERI rate is invalid"); - } - - function _requireCanIssueOnBehalf(address issueForAddress, address from) internal view { - require(delegateApprovals().canIssueFor(issueForAddress, from), "Not approved to act on behalf"); - } - - function _requireCanBurnOnBehalf(address burnForAddress, address from) internal view { - require(delegateApprovals().canBurnFor(burnForAddress, from), "Not approved to act on behalf"); - } - - function _requireCurrencyKeyIsNotpUSD(bytes32 _currencyKey) internal pure { - require(_currencyKey != pUSD, "pUSD is not staking coin"); - } - - function _requireNotExceedsQuotaLimit( - address _account, - uint _debtBalance, - uint _additionalpUSD, - uint _additionalExToken, - bool _isIssue - ) internal view { - uint estimatedExternalTokenQuota = - _externalTokenQuota(_account, _debtBalance, _additionalpUSD, _additionalExToken, _isIssue); - - bytes32[] memory tokenList = exTokenStakeManager().getTokenList(); - uint minDecimals = 18; - for (uint i = 0; i < tokenList.length; i++) { - uint decimals = exTokenStakeManager().getTokenDecimals(tokenList[i]); - - minDecimals = decimals < minDecimals ? decimals : minDecimals; - } - - require( - // due to the error caused by decimal difference, round down it upto minimum decimals among staking token list. - estimatedExternalTokenQuota.roundDownDecimal(uint(18).sub(minDecimals)) <= getExternalTokenQuota(), - "External token staking amount exceeds quota limit" - ); - } - - function _issuePynths( - address from, - uint amount, - uint maxIssuable, - uint existingDebt, - uint totalSystemDebt, - bool issueMax - ) internal returns (uint afterDebt) { - if (!issueMax) { - require(amount <= maxIssuable, "Amount too large"); - } else { - amount = maxIssuable; - } - - // Keep track of the debt they're about to create - _addToDebtRegister(from, amount, existingDebt, totalSystemDebt); - - // record issue timestamp - _setLastIssueEvent(from); - - // Create their pynths - pynths[pUSD].issue(from, amount); - - // Account for the issued debt in the cache - debtCache().updateCachedPynthDebtWithRate(pUSD, SafeDecimalMath.unit()); - - // Store their locked PERI amount to determine their fee % for the period - _appendAccountIssuanceRecord(from); - - afterDebt = existingDebt.add(amount); - } - - function _burnPynths( - address debtAccount, - address burnAccount, - uint amountBurnt, - uint existingDebt, - uint totalDebtIssued - ) internal returns (uint) { - // liquidation requires pUSD to be already settled / not in waiting period - - require(amountBurnt <= existingDebt, "Trying to burn more than debt"); - - // Remove liquidated debt from the ledger - _removeFromDebtRegister(debtAccount, amountBurnt, existingDebt, totalDebtIssued); - - // pynth.burn does a safe subtraction on balance (so it will revert if there are not enough pynths). - pynths[pUSD].burn(burnAccount, amountBurnt); - - // Account for the burnt debt in the cache. - debtCache().updateCachedPynthDebtWithRate(pUSD, SafeDecimalMath.unit()); - - // Store their debtRatio against a fee period to determine their fee/rewards % for the period - _appendAccountIssuanceRecord(debtAccount); - - return amountBurnt; - } - - // If burning to target, `amount` is ignored, and the correct quantity of pUSD is burnt to reach the target - // c-ratio, allowing fees to be claimed. In this case, pending settlements will be skipped as the user - // will still have debt remaining after reaching their target. - function _voluntaryBurnPynths( - address from, - uint amount, - bool burnToTarget, - bool burnMax - ) internal returns (uint remainingDebt) { - if (!burnToTarget) { - // If not burning to target, then burning requires that the minimum stake time has elapsed. - require(_canBurnPynths(from), "Minimum stake time not reached"); - // First settle anything pending into pUSD as burning or issuing impacts the size of the debt pool - (, uint refunded, uint numEntriesSettled) = exchanger().settle(from, pUSD); - if (numEntriesSettled > 0) { - amount = exchanger().calculateAmountAfterSettlement(from, pUSD, amount, refunded); - } - } - - (uint existingDebt, uint totalSystemValue, bool anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(from, pUSD); - (uint maxIssuablePynthsForAccount, bool periRateInvalid) = _maxIssuablePynths(from); - _requireRatesNotInvalid(anyRateIsInvalid || periRateInvalid); - require(existingDebt > 0, "No debt to forgive"); - - if (burnMax) { - amount = existingDebt; - } - - uint amountBurnt = _burnPynths(from, from, amount, existingDebt, totalSystemValue); - remainingDebt = existingDebt.sub(amountBurnt); - - // Check and remove liquidation if existingDebt after burning is <= maxIssuablePynths - // Issuance ratio is fixed so should remove any liquidations - if (existingDebt >= amountBurnt && remainingDebt <= maxIssuablePynthsForAccount) { - liquidations().removeAccountInLiquidation(from); - } - } - - function _setLastIssueEvent(address account) internal { - // Set the timestamp of the last issuePynths - flexibleStorage().setUIntValue( - CONTRACT_NAME, - keccak256(abi.encodePacked(LAST_ISSUE_EVENT, account)), - block.timestamp - ); - } - - function _appendAccountIssuanceRecord(address from) internal { - uint initialDebtOwnership; - uint debtEntryIndex; - (initialDebtOwnership, debtEntryIndex) = periFinanceState().issuanceData(from); - feePool().appendAccountIssuanceRecord(from, initialDebtOwnership, debtEntryIndex); - } - - function _addToDebtRegister( - address from, - uint amount, - uint existingDebt, - uint totalDebtIssued - ) internal { - IPeriFinanceState state = periFinanceState(); - - // What will the new total be including the new value? - uint newTotalDebtIssued = amount.add(totalDebtIssued); - - // What is their percentage (as a high precision int) of the total debt? - uint debtPercentage = amount.divideDecimalRoundPrecise(newTotalDebtIssued); - - // And what effect does this percentage change have on the global debt holding of other issuers? - // The delta specifically needs to not take into account any existing debt as it's already - // accounted for in the delta from when they issued previously. - // The delta is a high precision integer. - uint delta = SafeDecimalMath.preciseUnit().sub(debtPercentage); - - // And what does their debt ownership look like including this previous stake? - if (existingDebt > 0) { - debtPercentage = amount.add(existingDebt).divideDecimalRoundPrecise(newTotalDebtIssued); - } else { - // If they have no debt, they're a new issuer; record this. - state.incrementTotalIssuerCount(); - } - - // Save the debt entry parameters - state.setCurrentIssuanceData(from, debtPercentage); - - // And if we're the first, push 1 as there was no effect to any other holders, otherwise push - // the change for the rest of the debt holders. The debt ledger holds high precision integers. - if (state.debtLedgerLength() > 0 && state.lastDebtLedgerEntry() != 0) { - state.appendDebtLedgerValue(state.lastDebtLedgerEntry().multiplyDecimalRoundPrecise(delta)); - } else { - state.appendDebtLedgerValue(SafeDecimalMath.preciseUnit()); - } - } - - function _removeFromDebtRegister( - address from, - uint debtToRemove, - uint existingDebt, - uint totalDebtIssued - ) internal { - IPeriFinanceState state = periFinanceState(); - - // What will the new total after taking out the withdrawn amount - uint newTotalDebtIssued = totalDebtIssued.sub(debtToRemove); - - uint delta = 0; - - // What will the debt delta be if there is any debt left? - // Set delta to 0 if no more debt left in system after user - if (newTotalDebtIssued > 0) { - // What is the percentage of the withdrawn debt (as a high precision int) of the total debt after? - uint debtPercentage = debtToRemove.divideDecimalRoundPrecise(newTotalDebtIssued); - - // And what effect does this percentage change have on the global debt holding of other issuers? - // The delta specifically needs to not take into account any existing debt as it's already - // accounted for in the delta from when they issued previously. - delta = SafeDecimalMath.preciseUnit().add(debtPercentage); - } - - // Are they exiting the system, or are they just decreasing their debt position? - if (debtToRemove == existingDebt) { - state.setCurrentIssuanceData(from, 0); - state.decrementTotalIssuerCount(); - } else { - // What percentage of the debt will they be left with? - uint newDebt = existingDebt.sub(debtToRemove); - uint newDebtPercentage = newDebt.divideDecimalRoundPrecise(newTotalDebtIssued); - - // Store the debt percentage and debt ledger as high precision integers - state.setCurrentIssuanceData(from, newDebtPercentage); - } - - // Update our cumulative ledger. This is also a high precision integer. - state.appendDebtLedgerValue(state.lastDebtLedgerEntry().multiplyDecimalRoundPrecise(delta)); - } - - /* ========== MODIFIERS ========== */ - - function _onlyPeriFinance() internal view { - require(msg.sender == address(periFinance()), "Issuer: Only the periFinance contract can perform this action"); - } - - modifier onlyPeriFinance() { - _onlyPeriFinance(); // Use an internal function to save code size. - _; - } - - /* ========== EVENTS ========== */ - - event PynthAdded(bytes32 currencyKey, address pynth); - event PynthRemoved(bytes32 currencyKey, address pynth); -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BConst.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BConst.sol deleted file mode 100644 index 7f2b3f25d3..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BConst.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.6.0; - - -/************************************************************************************************ -Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BConst.sol - -This source code has been modified from the original, which was copied from the github repository -at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. - -Subject to the GPL-3.0 license -*************************************************************************************************/ - - -contract BConst { - uint256 public constant VERSION_NUMBER = 1; - -/* --- Weight Updates --- */ - - // Minimum time passed between each weight update for a token. - uint256 internal constant WEIGHT_UPDATE_DELAY = 30 minutes; - - // Maximum percent by which a weight can adjust at a time - // relative to the current weight. - // The number of iterations needed to move from weight A to weight B is the floor of: - // (A > B): (ln(A) - ln(B)) / ln(1.01) - // (B > A): (ln(A) - ln(B)) / ln(0.99) - uint256 internal constant WEIGHT_CHANGE_PCT = BONE/100; - - uint256 internal constant BONE = 10**18; - - uint256 internal constant MIN_BOUND_TOKENS = 2; - uint256 internal constant MAX_BOUND_TOKENS = 10; - - // Minimum swap fee. - uint256 internal constant MIN_FEE = BONE / 10**6; - // Maximum swap or exit fee. - uint256 internal constant MAX_FEE = BONE / 10; - // Actual exit fee. - uint256 internal constant EXIT_FEE = 5e15; - - // Default total of all desired weights. Can differ by up to BONE. - uint256 internal constant DEFAULT_TOTAL_WEIGHT = BONE * 25; - // Minimum weight for any token (1/100). - uint256 internal constant MIN_WEIGHT = BONE / 4; - uint256 internal constant MAX_WEIGHT = BONE * 25; - // Maximum total weight. - uint256 internal constant MAX_TOTAL_WEIGHT = BONE * 27; - // Minimum balance for a token (only applied at initialization) - uint256 internal constant MIN_BALANCE = BONE / 10**12; - // Initial pool tokens - uint256 internal constant INIT_POOL_SUPPLY = BONE * 100; - - uint256 internal constant MIN_BPOW_BASE = 1 wei; - uint256 internal constant MAX_BPOW_BASE = (2 * BONE) - 1 wei; - uint256 internal constant BPOW_PRECISION = BONE / 10**10; - - // Maximum ratio of input tokens to balance for swaps. - uint256 internal constant MAX_IN_RATIO = BONE / 2; - // Maximum ratio of output tokens to balance for swaps. - uint256 internal constant MAX_OUT_RATIO = (BONE / 3) + 1 wei; -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BMath.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BMath.sol deleted file mode 100644 index 47593291a2..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BMath.sol +++ /dev/null @@ -1,252 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.6.0; - -import "./BNum.sol"; - - -/************************************************************************************************ -Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BMath.sol - -This source code has been modified from the original, which was copied from the github repository -at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. - -Subject to the GPL-3.0 license -*************************************************************************************************/ - - -contract BMath is BConst, BNum { - /********************************************************************************************** - // calcSpotPrice // - // sP = spotPrice // - // bI = tokenBalanceIn ( bI / wI ) 1 // - // bO = tokenBalanceOut sP = ----------- * ---------- // - // wI = tokenWeightIn ( bO / wO ) ( 1 - sF ) // - // wO = tokenWeightOut // - // sF = swapFee // - **********************************************************************************************/ - function calcSpotPrice( - uint256 tokenBalanceIn, - uint256 tokenWeightIn, - uint256 tokenBalanceOut, - uint256 tokenWeightOut, - uint256 swapFee - ) internal pure returns (uint256 spotPrice) { - uint256 numer = bdiv(tokenBalanceIn, tokenWeightIn); - uint256 denom = bdiv(tokenBalanceOut, tokenWeightOut); - uint256 ratio = bdiv(numer, denom); - uint256 scale = bdiv(BONE, bsub(BONE, swapFee)); - return (spotPrice = bmul(ratio, scale)); - } - - /********************************************************************************************** - // calcOutGivenIn // - // aO = tokenAmountOut // - // bO = tokenBalanceOut // - // bI = tokenBalanceIn / / bI \ (wI / wO) \ // - // aI = tokenAmountIn aO = bO * | 1 - | -------------------------- | ^ | // - // wI = tokenWeightIn \ \ ( bI + ( aI * ( 1 - sF )) / / // - // wO = tokenWeightOut // - // sF = swapFee // - **********************************************************************************************/ - function calcOutGivenIn( - uint256 tokenBalanceIn, - uint256 tokenWeightIn, - uint256 tokenBalanceOut, - uint256 tokenWeightOut, - uint256 tokenAmountIn, - uint256 swapFee - ) internal pure returns (uint256 tokenAmountOut) { - uint256 weightRatio = bdiv(tokenWeightIn, tokenWeightOut); - uint256 adjustedIn = bsub(BONE, swapFee); - adjustedIn = bmul(tokenAmountIn, adjustedIn); - uint256 y = bdiv(tokenBalanceIn, badd(tokenBalanceIn, adjustedIn)); - uint256 foo = bpow(y, weightRatio); - uint256 bar = bsub(BONE, foo); - tokenAmountOut = bmul(tokenBalanceOut, bar); - return tokenAmountOut; - } - - /********************************************************************************************** - // calcInGivenOut // - // aI = tokenAmountIn // - // bO = tokenBalanceOut / / bO \ (wO / wI) \ // - // bI = tokenBalanceIn bI * | | ------------ | ^ - 1 | // - // aO = tokenAmountOut aI = \ \ ( bO - aO ) / / // - // wI = tokenWeightIn -------------------------------------------- // - // wO = tokenWeightOut ( 1 - sF ) // - // sF = swapFee // - **********************************************************************************************/ - function calcInGivenOut( - uint256 tokenBalanceIn, - uint256 tokenWeightIn, - uint256 tokenBalanceOut, - uint256 tokenWeightOut, - uint256 tokenAmountOut, - uint256 swapFee - ) internal pure returns (uint256 tokenAmountIn) { - uint256 weightRatio = bdiv(tokenWeightOut, tokenWeightIn); - uint256 diff = bsub(tokenBalanceOut, tokenAmountOut); - uint256 y = bdiv(tokenBalanceOut, diff); - uint256 foo = bpow(y, weightRatio); - foo = bsub(foo, BONE); - tokenAmountIn = bsub(BONE, swapFee); - tokenAmountIn = bdiv(bmul(tokenBalanceIn, foo), tokenAmountIn); - return tokenAmountIn; - } - - /********************************************************************************************** - // calcPoolOutGivenSingleIn // - // pAo = poolAmountOut / \ // - // tAi = tokenAmountIn /// / // wI \ \\ \ wI \ // - // wI = tokenWeightIn //| tAi *| 1 - || 1 - -- | * sF || + tBi \ -- \ // - // tW = totalWeight pAo=|| \ \ \\ tW / // | ^ tW | * pS - pS // - // tBi = tokenBalanceIn \\ ------------------------------------- / / // - // pS = poolSupply \\ tBi / / // - // sF = swapFee \ / // - **********************************************************************************************/ - function calcPoolOutGivenSingleIn( - uint256 tokenBalanceIn, - uint256 tokenWeightIn, - uint256 poolSupply, - uint256 totalWeight, - uint256 tokenAmountIn, - uint256 swapFee - ) internal pure returns (uint256 poolAmountOut) { - // Charge the trading fee for the proportion of tokenAi - /// which is implicitly traded to the other pool tokens. - // That proportion is (1- weightTokenIn) - // tokenAiAfterFee = tAi * (1 - (1-weightTi) * poolFee); - uint256 normalizedWeight = bdiv(tokenWeightIn, totalWeight); - uint256 zaz = bmul(bsub(BONE, normalizedWeight), swapFee); - uint256 tokenAmountInAfterFee = bmul(tokenAmountIn, bsub(BONE, zaz)); - - uint256 newTokenBalanceIn = badd(tokenBalanceIn, tokenAmountInAfterFee); - uint256 tokenInRatio = bdiv(newTokenBalanceIn, tokenBalanceIn); - - // uint newPoolSupply = (ratioTi ^ weightTi) * poolSupply; - uint256 poolRatio = bpow(tokenInRatio, normalizedWeight); - uint256 newPoolSupply = bmul(poolRatio, poolSupply); - poolAmountOut = bsub(newPoolSupply, poolSupply); - return poolAmountOut; - } - - /********************************************************************************************** - // calcSingleInGivenPoolOut // - // tAi = tokenAmountIn //(pS + pAo)\ / 1 \\ // - // pS = poolSupply || --------- | ^ | --------- || * bI - bI // - // pAo = poolAmountOut \\ pS / \(wI / tW)// // - // bI = balanceIn tAi = -------------------------------------------- // - // wI = weightIn / wI \ // - // tW = totalWeight | 1 - ---- | * sF // - // sF = swapFee \ tW / // - **********************************************************************************************/ - function calcSingleInGivenPoolOut( - uint256 tokenBalanceIn, - uint256 tokenWeightIn, - uint256 poolSupply, - uint256 totalWeight, - uint256 poolAmountOut, - uint256 swapFee - ) internal pure returns (uint256 tokenAmountIn) { - uint256 normalizedWeight = bdiv(tokenWeightIn, totalWeight); - uint256 newPoolSupply = badd(poolSupply, poolAmountOut); - uint256 poolRatio = bdiv(newPoolSupply, poolSupply); - - //uint newBalTi = poolRatio^(1/weightTi) * balTi; - uint256 boo = bdiv(BONE, normalizedWeight); - uint256 tokenInRatio = bpow(poolRatio, boo); - uint256 newTokenBalanceIn = bmul(tokenInRatio, tokenBalanceIn); - uint256 tokenAmountInAfterFee = bsub(newTokenBalanceIn, tokenBalanceIn); - // Do reverse order of fees charged in joinswap_ExternAmountIn, this way - // ``` pAo == joinswap_ExternAmountIn(Ti, joinswap_PoolAmountOut(pAo, Ti)) ``` - //uint tAi = tAiAfterFee / (1 - (1-weightTi) * swapFee) ; - uint256 zar = bmul(bsub(BONE, normalizedWeight), swapFee); - tokenAmountIn = bdiv(tokenAmountInAfterFee, bsub(BONE, zar)); - return tokenAmountIn; - } - - /********************************************************************************************** - // calcSingleOutGivenPoolIn // - // tAo = tokenAmountOut / / \\ // - // bO = tokenBalanceOut / // pS - (pAi * (1 - eF)) \ / 1 \ \\ // - // pAi = poolAmountIn | bO - || ----------------------- | ^ | --------- | * b0 || // - // ps = poolSupply \ \\ pS / \(wO / tW)/ // // - // wI = tokenWeightIn tAo = \ \ // // - // tW = totalWeight / / wO \ \ // - // sF = swapFee * | 1 - | 1 - ---- | * sF | // - // eF = exitFee \ \ tW / / // - **********************************************************************************************/ - function calcSingleOutGivenPoolIn( - uint256 tokenBalanceOut, - uint256 tokenWeightOut, - uint256 poolSupply, - uint256 totalWeight, - uint256 poolAmountIn, - uint256 swapFee - ) internal pure returns (uint256 tokenAmountOut) { - uint256 normalizedWeight = bdiv(tokenWeightOut, totalWeight); - // charge exit fee on the pool token side - // pAiAfterExitFee = pAi*(1-exitFee) - uint256 poolAmountInAfterExitFee = bmul(poolAmountIn, bsub(BONE, EXIT_FEE)); - uint256 newPoolSupply = bsub(poolSupply, poolAmountInAfterExitFee); - uint256 poolRatio = bdiv(newPoolSupply, poolSupply); - - // newBalTo = poolRatio^(1/weightTo) * balTo; - uint256 tokenOutRatio = bpow(poolRatio, bdiv(BONE, normalizedWeight)); - uint256 newTokenBalanceOut = bmul(tokenOutRatio, tokenBalanceOut); - - uint256 tokenAmountOutBeforeSwapFee = bsub( - tokenBalanceOut, - newTokenBalanceOut - ); - - // charge swap fee on the output token side - //uint tAo = tAoBeforeSwapFee * (1 - (1-weightTo) * swapFee) - uint256 zaz = bmul(bsub(BONE, normalizedWeight), swapFee); - tokenAmountOut = bmul(tokenAmountOutBeforeSwapFee, bsub(BONE, zaz)); - return tokenAmountOut; - } - - /********************************************************************************************** - // calcPoolInGivenSingleOut // - // pAi = poolAmountIn // / tAo \\ / wO \ \ // - // bO = tokenBalanceOut // | bO - -------------------------- |\ | ---- | \ // - // tAo = tokenAmountOut pS - || \ 1 - ((1 - (tO / tW)) * sF)/ | ^ \ tW / * pS | // - // ps = poolSupply \\ -----------------------------------/ / // - // wO = tokenWeightOut pAi = \\ bO / / // - // tW = totalWeight ------------------------------------------------------------- // - // sF = swapFee ( 1 - eF ) // - // eF = exitFee // - **********************************************************************************************/ - function calcPoolInGivenSingleOut( - uint256 tokenBalanceOut, - uint256 tokenWeightOut, - uint256 poolSupply, - uint256 totalWeight, - uint256 tokenAmountOut, - uint256 swapFee - ) internal pure returns (uint256 poolAmountIn) { - // charge swap fee on the output token side - uint256 normalizedWeight = bdiv(tokenWeightOut, totalWeight); - //uint tAoBeforeSwapFee = tAo / (1 - (1-weightTo) * swapFee) ; - uint256 zoo = bsub(BONE, normalizedWeight); - uint256 zar = bmul(zoo, swapFee); - uint256 tokenAmountOutBeforeSwapFee = bdiv(tokenAmountOut, bsub(BONE, zar)); - - uint256 newTokenBalanceOut = bsub( - tokenBalanceOut, - tokenAmountOutBeforeSwapFee - ); - uint256 tokenOutRatio = bdiv(newTokenBalanceOut, tokenBalanceOut); - - //uint newPoolSupply = (ratioTo ^ weightTo) * poolSupply; - uint256 poolRatio = bpow(tokenOutRatio, normalizedWeight); - uint256 newPoolSupply = bmul(poolRatio, poolSupply); - uint256 poolAmountInAfterExitFee = bsub(poolSupply, newPoolSupply); - - // charge exit fee on the pool token side - // pAi = pAiAfterExitFee/(1-exitFee) - poolAmountIn = bdiv(poolAmountInAfterExitFee, bsub(BONE, EXIT_FEE)); - return poolAmountIn; - } -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BNum.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BNum.sol deleted file mode 100644 index 8dfe7689d0..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BNum.sol +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.6.0; - -import "./BConst.sol"; - - -/************************************************************************************************ -Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BNum.sol - -This source code has been modified from the original, which was copied from the github repository -at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. - -Subject to the GPL-3.0 license -*************************************************************************************************/ - - -contract BNum is BConst { - function btoi(uint256 a) internal pure returns (uint256) { - return a / BONE; - } - - function bfloor(uint256 a) internal pure returns (uint256) { - return btoi(a) * BONE; - } - - function badd(uint256 a, uint256 b) internal pure returns (uint256) { - uint256 c = a + b; - require(c >= a, "ERR_ADD_OVERFLOW"); - return c; - } - - function bsub(uint256 a, uint256 b) internal pure returns (uint256) { - (uint256 c, bool flag) = bsubSign(a, b); - require(!flag, "ERR_SUB_UNDERFLOW"); - return c; - } - - function bsubSign(uint256 a, uint256 b) - internal - pure - returns (uint256, bool) - { - if (a >= b) { - return (a - b, false); - } else { - return (b - a, true); - } - } - - function bmul(uint256 a, uint256 b) internal pure returns (uint256) { - uint256 c0 = a * b; - require(a == 0 || c0 / a == b, "ERR_MUL_OVERFLOW"); - uint256 c1 = c0 + (BONE / 2); - require(c1 >= c0, "ERR_MUL_OVERFLOW"); - uint256 c2 = c1 / BONE; - return c2; - } - - function bdiv(uint256 a, uint256 b) internal pure returns (uint256) { - require(b != 0, "ERR_DIV_ZERO"); - uint256 c0 = a * BONE; - require(a == 0 || c0 / a == BONE, "ERR_DIV_INTERNAL"); // bmul overflow - uint256 c1 = c0 + (b / 2); - require(c1 >= c0, "ERR_DIV_INTERNAL"); // badd require - uint256 c2 = c1 / b; - return c2; - } - - // DSMath.wpow - function bpowi(uint256 a, uint256 n) internal pure returns (uint256) { - uint256 z = n % 2 != 0 ? a : BONE; - - for (n /= 2; n != 0; n /= 2) { - a = bmul(a, a); - - if (n % 2 != 0) { - z = bmul(z, a); - } - } - return z; - } - - // Compute b^(e.w) by splitting it into (b^e)*(b^0.w). - // Use `bpowi` for `b^e` and `bpowK` for k iterations - // of approximation of b^0.w - function bpow(uint256 base, uint256 exp) internal pure returns (uint256) { - require(base >= MIN_BPOW_BASE, "ERR_BPOW_BASE_TOO_LOW"); - require(base <= MAX_BPOW_BASE, "ERR_BPOW_BASE_TOO_HIGH"); - - uint256 whole = bfloor(exp); - uint256 remain = bsub(exp, whole); - - uint256 wholePow = bpowi(base, btoi(whole)); - - if (remain == 0) { - return wholePow; - } - - uint256 partialResult = bpowApprox(base, remain, BPOW_PRECISION); - return bmul(wholePow, partialResult); - } - - function bpowApprox( - uint256 base, - uint256 exp, - uint256 precision - ) internal pure returns (uint256) { - // term 0: - uint256 a = exp; - (uint256 x, bool xneg) = bsubSign(base, BONE); - uint256 term = BONE; - uint256 sum = term; - bool negative = false; - - // term(k) = numer / denom - // = (product(a - i - 1, i=1-->k) * x^k) / (k!) - // each iteration, multiply previous term by (a-(k-1)) * x / k - // continue until term is less than precision - for (uint256 i = 1; term >= precision; i++) { - uint256 bigK = i * BONE; - (uint256 c, bool cneg) = bsubSign(a, bsub(bigK, BONE)); - term = bmul(term, bmul(c, x)); - term = bdiv(term, bigK); - if (term == 0) break; - - if (xneg) negative = !negative; - if (cneg) negative = !negative; - if (negative) { - sum = bsub(sum, term); - } else { - sum = badd(sum, term); - } - } - - return sum; - } -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BToken.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BToken.sol deleted file mode 100644 index 1053a7f002..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BToken.sol +++ /dev/null @@ -1,192 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.6.0; - -import "./BNum.sol"; - - -/************************************************************************************************ -Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BToken.sol - -This source code has been modified from the original, which was copied from the github repository -at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. - -Subject to the GPL-3.0 license -*************************************************************************************************/ - - -// Highly opinionated token implementation -interface IERC20 { - event Approval(address indexed src, address indexed dst, uint256 amt); - event Transfer(address indexed src, address indexed dst, uint256 amt); - - function name() external view returns (string memory); - - function symbol() external view returns (string memory); - - function decimals() external view returns (uint8); - - function totalSupply() external view returns (uint256); - - function balanceOf(address whom) external view returns (uint256); - - function allowance(address src, address dst) external view returns (uint256); - - function approve(address dst, uint256 amt) external returns (bool); - - function transfer(address dst, uint256 amt) external returns (bool); - - function transferFrom( - address src, - address dst, - uint256 amt - ) external returns (bool); -} - - -contract BTokenBase is BNum { - mapping(address => uint256) internal _balance; - mapping(address => mapping(address => uint256)) internal _allowance; - uint256 internal _totalSupply; - - event Approval(address indexed src, address indexed dst, uint256 amt); - event Transfer(address indexed src, address indexed dst, uint256 amt); - - function _mint(uint256 amt) internal { - _balance[address(this)] = badd(_balance[address(this)], amt); - _totalSupply = badd(_totalSupply, amt); - emit Transfer(address(0), address(this), amt); - } - - function _burn(uint256 amt) internal { - require(_balance[address(this)] >= amt, "ERR_INSUFFICIENT_BAL"); - _balance[address(this)] = bsub(_balance[address(this)], amt); - _totalSupply = bsub(_totalSupply, amt); - emit Transfer(address(this), address(0), amt); - } - - function _move( - address src, - address dst, - uint256 amt - ) internal { - require(_balance[src] >= amt, "ERR_INSUFFICIENT_BAL"); - _balance[src] = bsub(_balance[src], amt); - _balance[dst] = badd(_balance[dst], amt); - emit Transfer(src, dst, amt); - } - - function _push(address to, uint256 amt) internal { - _move(address(this), to, amt); - } - - function _pull(address from, uint256 amt) internal { - _move(from, address(this), amt); - } -} - - -contract BToken is BTokenBase, IERC20 { - uint8 private constant DECIMALS = 18; - string private _name; - string private _symbol; - - function _initializeToken(string memory name, string memory symbol) internal { - require( - bytes(_name).length == 0 && - bytes(name).length != 0 && - bytes(symbol).length != 0, - "ERR_BTOKEN_INITIALIZED" - ); - _name = name; - _symbol = symbol; - } - - function name() - external - override - view - returns (string memory) - { - return _name; - } - - function symbol() - external - override - view - returns (string memory) - { - return _symbol; - } - - function decimals() - external - override - view - returns (uint8) - { - return DECIMALS; - } - - function allowance(address src, address dst) - external - override - view - returns (uint256) - { - return _allowance[src][dst]; - } - - function balanceOf(address whom) external override view returns (uint256) { - return _balance[whom]; - } - - function totalSupply() public override view returns (uint256) { - return _totalSupply; - } - - function approve(address dst, uint256 amt) external override returns (bool) { - _allowance[msg.sender][dst] = amt; - emit Approval(msg.sender, dst, amt); - return true; - } - - function increaseApproval(address dst, uint256 amt) external returns (bool) { - _allowance[msg.sender][dst] = badd(_allowance[msg.sender][dst], amt); - emit Approval(msg.sender, dst, _allowance[msg.sender][dst]); - return true; - } - - function decreaseApproval(address dst, uint256 amt) external returns (bool) { - uint256 oldValue = _allowance[msg.sender][dst]; - if (amt > oldValue) { - _allowance[msg.sender][dst] = 0; - } else { - _allowance[msg.sender][dst] = bsub(oldValue, amt); - } - emit Approval(msg.sender, dst, _allowance[msg.sender][dst]); - return true; - } - - function transfer(address dst, uint256 amt) external override returns (bool) { - _move(msg.sender, dst, amt); - return true; - } - - function transferFrom( - address src, - address dst, - uint256 amt - ) external override returns (bool) { - require( - msg.sender == src || amt <= _allowance[src][msg.sender], - "ERR_BTOKEN_BAD_CALLER" - ); - _move(src, dst, amt); - if (msg.sender != src && _allowance[src][msg.sender] != uint256(-1)) { - _allowance[src][msg.sender] = bsub(_allowance[src][msg.sender], amt); - emit Approval(msg.sender, dst, _allowance[src][msg.sender]); - } - return true; - } -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BeefyVault.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BeefyVault.sol deleted file mode 100644 index d855711d9c..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/BeefyVault.sol +++ /dev/null @@ -1,941 +0,0 @@ -/** - *Submitted for verification at BscScan.com on 2020-10-27 -*/ - -// SPDX-License-Identifier: MIT -// File: @openzeppelin/contracts/GSN/Context.sol - - -pragma solidity ^0.6.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/token/ERC20/IERC20.sol - - -pragma solidity ^0.6.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; - -/** - * @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; - } -} - -// File: @openzeppelin/contracts/utils/Address.sol - - -pragma solidity ^0.6.2; - -/** - * @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); - } - } - } -} - -// File: @openzeppelin/contracts/token/ERC20/ERC20.sol - - -pragma solidity ^0.6.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 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 { - using SafeMath for uint256; - using Address for address; - - mapping (address => uint256) private _balances; - - mapping (address => mapping (address => uint256)) private _allowances; - - uint256 private _totalSupply; - - string private _name; - string private _symbol; - uint8 private _decimals; - - /** - * @dev Sets the values for {name} and {symbol}, initializes {decimals} with - * a default value of 18. - * - * To select a different value for {decimals}, use {_setupDecimals}. - * - * All three of these values are immutable: they can only be set once during - * construction. - */ - constructor (string memory name, string memory symbol) public { - _name = name; - _symbol = symbol; - _decimals = 18; - } - - /** - * @dev Returns the name of the token. - */ - function name() public view returns (string memory) { - return _name; - } - - /** - * @dev Returns the symbol of the token, usually a shorter version of the - * name. - */ - function symbol() public view 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 {_setupDecimals} is - * called. - * - * 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 returns (uint8) { - return _decimals; - } - - /** - * @dev See {IERC20-totalSupply}. - */ - function totalSupply() public view override returns (uint256) { - return _totalSupply; - } - - /** - * @dev See {IERC20-balanceOf}. - */ - function balanceOf(address account) public view 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); - _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); - 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].add(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) { - _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); - 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); - - _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); - _balances[recipient] = _balances[recipient].add(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 = _totalSupply.add(amount); - _balances[account] = _balances[account].add(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); - - _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); - _totalSupply = _totalSupply.sub(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 Sets {decimals} to a value other than the default one of 18. - * - * WARNING: This function should only be called from the constructor. Most - * applications that interact with token contracts will not expect - * {decimals} to ever change, and may work incorrectly if it does. - */ - function _setupDecimals(uint8 decimals_) internal { - _decimals = decimals_; - } - - /** - * @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: @openzeppelin/contracts/token/ERC20/SafeERC20.sol - - -pragma solidity ^0.6.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/BIFI/interfaces/beefy/IStrategy.sol - - -pragma solidity ^0.6.0; - -interface IStrategy { - function want() external view returns (address); - function deposit() external; - function withdraw(uint256) external; - function balanceOf() external view returns (uint256); -} - -// File: contracts/BIFI/vaults/BeefyVault.sol - - -pragma solidity ^0.6.0; - - - - - - -/** - * @dev Implementation of a vault to deposit funds for yield optimizing. - * This is the contract that receives funds and that users interface with. - * The yield optimizing strategy itself is implemented in a separate 'Strategy.sol' contract. - */ -contract BeefyVault is ERC20 { - using SafeERC20 for IERC20; - using Address for address; - using SafeMath for uint256; - - IERC20 public token; - address public strategy; - - /** - * @dev Sets the value of {token} to the token that the vault will - * hold as underlying value. It initializes the vault's own 'moo' token. - * This token is minted when someone does a deposit. It is burned in order - * to withdraw the corresponding portion of the underlying assets. - * @param _token the token to maximize. - * @param _strategy the address of the strategy. - * @param _name the name of the vault token. - * @param _symbol the symbol of the vault token. - */ - constructor (address _token, address _strategy, string memory _name, string memory _symbol) public ERC20( - string(abi.encodePacked(_name)), - string(abi.encodePacked(_symbol)) - ) { - token = IERC20(_token); - strategy = _strategy; - } - - /** - * @dev It calculates the total underlying value of {token} held by the system. - * It takes into account the vault contract balance, the strategy contract balance - * and the balance deployed in other contracts as part of the strategy. - */ - function balance() public view returns (uint) { - return token.balanceOf(address(this)) - .add(IStrategy(strategy).balanceOf()); - } - - /** - * @dev Custom logic in here for how much the vault allows to be borrowed. - * We return 100% of tokens for now. Under certain conditions we might - * want to keep some of the system funds at hand in the vault, instead - * of putting them to work. - */ - function available() public view returns (uint) { - return token.balanceOf(address(this)); - } - - /** - * @dev Function for various UIs to display the current value of one of our yield tokens. - * Returns an uint with 18 decimals of how much underlying asset one vault share represents. - */ - function getPricePerFullShare() public view returns (uint) { - return balance().mul(1e18).div(totalSupply()); - } - - /** - * @dev A helper function to call deposit() with all the sender's funds. - */ - function depositAll() external { - deposit(token.balanceOf(msg.sender)); - } - - /** - * @dev The entrypoint of funds into the system. People deposit with this function - * into the vault. The vault is then in charge of sending funds into the strategy. - */ - function deposit(uint _amount) public { - uint _pool = balance(); - uint _before = token.balanceOf(address(this)); - token.safeTransferFrom(msg.sender, address(this), _amount); - uint _after = token.balanceOf(address(this)); - _amount = _after.sub(_before); // Additional check for deflationary tokens - uint shares = 0; - if (totalSupply() == 0) { - shares = _amount; - } else { - shares = (_amount.mul(totalSupply())).div(_pool); - } - _mint(msg.sender, shares); - - earn(); - } - - /** - * @dev Function to send funds into the strategy and put them to work. It's primarily called - * by the vault's deposit() function. - */ - function earn() public { - uint _bal = available(); - token.safeTransfer(strategy, _bal); - IStrategy(strategy).deposit(); - } - - /** - * @dev A helper function to call withdraw() with all the sender's funds. - */ - function withdrawAll() external { - withdraw(balanceOf(msg.sender)); - } - - /** - * @dev Function to exit the system. The vault will withdraw the required tokens - * from the strategy and pay up the token holder. A proportional number of IOU - * tokens are burned in the process. - */ - function withdraw(uint _shares) public { - uint _withdraw = (balance().mul(_shares)).div(totalSupply()); - _burn(msg.sender, _shares); - - uint _before = token.balanceOf(address(this)); - IStrategy(strategy).withdraw(_withdraw); - uint _after = token.balanceOf(address(this)); - uint _diff = _after.sub(_before); - - token.safeTransfer(msg.sender, _diff); - } -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/ICompLikeToken.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/ICompLikeToken.sol deleted file mode 100644 index f98b034d68..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/ICompLikeToken.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.0; - - -interface ICompLikeToken { - function delegate(address delegatee) external; -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/IIndexPool.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/IIndexPool.sol deleted file mode 100644 index 3a499c389b..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/IIndexPool.sol +++ /dev/null @@ -1,202 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.6.0; -pragma experimental ABIEncoderV2; - - -interface IIndexPool { - /** - * @dev Token record data structure - * @param bound is token bound to pool - * @param ready has token been initialized - * @param lastDenormUpdate timestamp of last denorm change - * @param denorm denormalized weight - * @param desiredDenorm desired denormalized weight (used for incremental changes) - * @param index index of address in tokens array - * @param balance token balance - */ - struct Record { - bool bound; - bool ready; - uint40 lastDenormUpdate; - uint96 denorm; - uint96 desiredDenorm; - uint8 index; - uint256 balance; - } - -/* ========== EVENTS ========== */ - - /** @dev Emitted when tokens are swapped. */ - event LOG_SWAP( - address indexed caller, - address indexed tokenIn, - address indexed tokenOut, - uint256 tokenAmountIn, - uint256 tokenAmountOut - ); - - /** @dev Emitted when underlying tokens are deposited for pool tokens. */ - event LOG_JOIN( - address indexed caller, - address indexed tokenIn, - uint256 tokenAmountIn - ); - - /** @dev Emitted when pool tokens are burned for underlying. */ - event LOG_EXIT( - address indexed caller, - address indexed tokenOut, - uint256 tokenAmountOut - ); - - /** @dev Emitted when a token's weight updates. */ - event LOG_DENORM_UPDATED(address indexed token, uint256 newDenorm); - - /** @dev Emitted when a token's desired weight is set. */ - event LOG_DESIRED_DENORM_SET(address indexed token, uint256 desiredDenorm); - - /** @dev Emitted when a token is unbound from the pool. */ - event LOG_TOKEN_REMOVED(address token); - - /** @dev Emitted when a token is unbound from the pool. */ - event LOG_TOKEN_ADDED( - address indexed token, - uint256 desiredDenorm, - uint256 minimumBalance - ); - - /** @dev Emitted when a token's minimum balance is updated. */ - event LOG_MINIMUM_BALANCE_UPDATED(address token, uint256 minimumBalance); - - /** @dev Emitted when a token reaches its minimum balance. */ - event LOG_TOKEN_READY(address indexed token); - - /** @dev Emitted when public trades are enabled. */ - event LOG_PUBLIC_SWAP_ENABLED(); - - /** @dev Emitted when the swap fee is updated. */ - event LOG_SWAP_FEE_UPDATED(uint256 swapFee); - - /** @dev Emitted when exit fee recipient is updated. */ - event LOG_EXIT_FEE_RECIPIENT_UPDATED(address exitFeeRecipient); - - /** @dev Emitted when controller is updated. */ - event LOG_CONTROLLER_UPDATED(address exitFeeRecipient); - - function configure( - address controller, - string calldata name, - string calldata symbol, - address exitFeeRecipient - ) external; - - function initialize( - address[] calldata tokens, - uint256[] calldata balances, - uint96[] calldata denorms, - address tokenProvider, - address unbindHandler - ) external; - - function setSwapFee(uint256 swapFee) external; - - function delegateCompLikeToken(address token, address delegatee) external; - - function setExitFeeRecipient(address exitFeeRecipient) external; - - function setController(address controller) external; - - function reweighTokens( - address[] calldata tokens, - uint96[] calldata desiredDenorms - ) external; - - function reindexTokens( - address[] calldata tokens, - uint96[] calldata desiredDenorms, - uint256[] calldata minimumBalances - ) external; - - function setMinimumBalance(address token, uint256 minimumBalance) external; - - function joinPool(uint256 poolAmountOut, uint256[] calldata maxAmountsIn) external; - - function joinswapExternAmountIn( - address tokenIn, - uint256 tokenAmountIn, - uint256 minPoolAmountOut - ) external returns (uint256/* poolAmountOut */); - - 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 gulp(address token) external; - - function swapExactAmountIn( - address tokenIn, - uint256 tokenAmountIn, - address tokenOut, - uint256 minAmountOut, - uint256 maxPrice - ) external returns (uint256/* tokenAmountOut */, uint256/* spotPriceAfter */); - - function swapExactAmountOut( - address tokenIn, - uint256 maxAmountIn, - address tokenOut, - uint256 tokenAmountOut, - uint256 maxPrice - ) external returns (uint256 /* tokenAmountIn */, uint256 /* spotPriceAfter */); - - function isPublicSwap() external view returns (bool); - - function getSwapFee() external view returns (uint256/* swapFee */); - - function getExitFee() external view returns (uint256/* exitFee */); - - function getController() external view returns (address); - - function getExitFeeRecipient() external view returns (address); - - function isBound(address t) external view returns (bool); - - function getNumTokens() external view returns (uint256); - - function getCurrentTokens() external view returns (address[] memory tokens); - - function getCurrentDesiredTokens() external view returns (address[] memory tokens); - - function getDenormalizedWeight(address token) external view returns (uint256/* denorm */); - - function getTokenRecord(address token) external view returns (Record memory record); - - function extrapolatePoolValueFromToken() external view returns (address/* token */, uint256/* extrapolatedValue */); - - function getTotalDenormalizedWeight() external view returns (uint256); - - function getBalance(address token) external view returns (uint256); - - function getMinimumBalance(address token) external view returns (uint256); - - function getUsedBalance(address token) external view returns (uint256); - - function getSpotPrice(address tokenIn, address tokenOut) external view returns (uint256); -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/IndexPool.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/IndexPool.sol deleted file mode 100644 index 647c59bdb3..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/IndexPool.sol +++ /dev/null @@ -1,1368 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.6.0; -pragma experimental ABIEncoderV2; - -/* ========== Internal Inheritance ========== */ -import "./BToken.sol"; -import "./BMath.sol"; - -/* ========== Internal Interfaces ========== */ -import "./IIndexPool.sol"; -import "./ICompLikeToken.sol"; - - -/************************************************************************************************ -Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol - -This source code has been modified from the original, which was copied from the github repository -at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. - -Subject to the GPL-3.0 license -*************************************************************************************************/ - - -contract IndexPool is BToken, BMath, IIndexPool { -/* ========== Modifiers ========== */ - - modifier _lock_ { - require(!_mutex, "ERR_REENTRY"); - _mutex = true; - _; - _mutex = false; - } - - modifier _viewlock_() { - require(!_mutex, "ERR_REENTRY"); - _; - } - - modifier _control_ { - require(msg.sender == _controller, "ERR_NOT_CONTROLLER"); - _; - } - - modifier _public_ { - require(_publicSwap, "ERR_NOT_PUBLIC"); - _; - } - -/* ========== Storage ========== */ - - bool internal _mutex; - - // Account with CONTROL role. Able to modify the swap fee, - // adjust token weights, bind and unbind tokens and lock - // public swaps & joins. - address internal _controller; - - // Contract that handles unbound tokens. - TokenUnbindHandler internal _unbindHandler; - - // True if PUBLIC can call SWAP & JOIN functions - bool internal _publicSwap; - - // `setSwapFee` requires CONTROL - uint256 internal _swapFee; - - // Array of underlying tokens in the pool. - address[] internal _tokens; - - // Internal records of the pool's underlying tokens - mapping(address => Record) internal _records; - - // Total denormalized weight of the pool. - uint256 internal _totalWeight; - - // Minimum balances for tokens which have been added without the - // requisite initial balance. - mapping(address => uint256) internal _minimumBalances; - - // Recipient for exit fees - address internal _exitFeeRecipient; - -/* ========== Controls ========== */ - - /** - * @dev Sets the controller address and the token name & symbol. - * - * Note: This saves on storage costs for multi-step pool deployment. - * - * @param controller Controller of the pool - * @param name Name of the pool token - * @param symbol Symbol of the pool token - * @param exitFeeRecipient Address that receives exit fees - */ - function configure( - address controller, - string calldata name, - string calldata symbol, - address exitFeeRecipient - ) external override { - require(_controller == address(0), "ERR_CONFIGURED"); - require(controller != address(0) && exitFeeRecipient != address(0), "ERR_NULL_ADDRESS"); - _controller = controller; - // default fee is 2% - _swapFee = BONE / 50; - _exitFeeRecipient = exitFeeRecipient; - _initializeToken(name, symbol); - } - - /** - * @dev Sets up the initial assets for the pool. - * - * Note: `tokenProvider` must have approved the pool to transfer the - * corresponding `balances` of `tokens`. - * - * @param tokens Underlying tokens to initialize the pool with - * @param balances Initial balances to transfer - * @param denorms Initial denormalized weights for the tokens - * @param tokenProvider Address to transfer the balances from - */ - function initialize( - address[] calldata tokens, - uint256[] calldata balances, - uint96[] calldata denorms, - address tokenProvider, - address unbindHandler - ) - external - override - _control_ - { - require(_tokens.length == 0, "ERR_INITIALIZED"); - uint256 len = tokens.length; - require(len >= MIN_BOUND_TOKENS, "ERR_MIN_TOKENS"); - require(len <= MAX_BOUND_TOKENS, "ERR_MAX_TOKENS"); - require(balances.length == len && denorms.length == len, "ERR_ARR_LEN"); - uint256 totalWeight = 0; - for (uint256 i = 0; i < len; i++) { - address token = tokens[i]; - uint96 denorm = denorms[i]; - uint256 balance = balances[i]; - require(denorm >= MIN_WEIGHT, "ERR_MIN_WEIGHT"); - require(denorm <= MAX_WEIGHT, "ERR_MAX_WEIGHT"); - require(balance >= MIN_BALANCE, "ERR_MIN_BALANCE"); - _records[token] = Record({ - bound: true, - ready: true, - lastDenormUpdate: uint40(now), - denorm: denorm, - desiredDenorm: denorm, - index: uint8(i), - balance: balance - }); - _tokens.push(token); - totalWeight = badd(totalWeight, denorm); - _pullUnderlying(token, tokenProvider, balance); - } - require(totalWeight <= MAX_TOTAL_WEIGHT, "ERR_MAX_TOTAL_WEIGHT"); - _totalWeight = totalWeight; - _publicSwap = true; - emit LOG_PUBLIC_SWAP_ENABLED(); - _mintPoolShare(INIT_POOL_SUPPLY); - _pushPoolShare(tokenProvider, INIT_POOL_SUPPLY); - _unbindHandler = TokenUnbindHandler(unbindHandler); - } - - /** - * @dev Set the swap fee. - * Note: Swap fee must be between 0.0001% and 10% - */ - function setSwapFee(uint256 swapFee) external override _control_ { - require(swapFee >= MIN_FEE && swapFee <= MAX_FEE, "ERR_INVALID_FEE"); - _swapFee = swapFee; - emit LOG_SWAP_FEE_UPDATED(swapFee); - } - - /** - * @dev Delegate a comp-like governance token to an address - * specified by the controller. - */ - function delegateCompLikeToken(address token,address delegatee) - external - override - _control_ - { - ICompLikeToken(token).delegate(delegatee); - } - - /** - * @dev Set the exit fee recipient address. - */ - function setExitFeeRecipient(address exitFeeRecipient) external override _control_ { - require(exitFeeRecipient != address(0), "ERR_NULL_ADDRESS"); - _exitFeeRecipient = exitFeeRecipient; - emit LOG_EXIT_FEE_RECIPIENT_UPDATED(exitFeeRecipient); - } - - /** - * @dev Set the controller address - */ - function setController(address controller) external override _control_ { - require(controller != address(0), "ERR_NULL_ADDRESS"); - _controller = controller; - emit LOG_CONTROLLER_UPDATED(controller); - } - -/* ========== Token Management Actions ========== */ - - /** - * @dev Sets the desired weights for the pool tokens, which - * will be adjusted over time as they are swapped. - * - * Note: This does not check for duplicate tokens or that the total - * of the desired weights is equal to the target total weight (25). - * Those assumptions should be met in the controller. Further, the - * provided tokens should only include the tokens which are not set - * for removal. - */ - function reweighTokens( - address[] calldata tokens, - uint96[] calldata desiredDenorms - ) - external - override - _lock_ - _control_ - { - require(desiredDenorms.length == tokens.length, "ERR_ARR_LEN"); - for (uint256 i = 0; i < tokens.length; i++) - _setDesiredDenorm(tokens[i], desiredDenorms[i]); - } - - /** - * @dev Update the underlying assets held by the pool and their associated - * weights. Tokens which are not currently bound will be gradually added - * as they are swapped in to reach the provided minimum balances, which must - * be an amount of tokens worth the minimum weight of the total pool value. - * If a currently bound token is not received in this call, the token's - * desired weight will be set to 0. - */ - function reindexTokens( - address[] calldata tokens, - uint96[] calldata desiredDenorms, - uint256[] calldata minimumBalances - ) - external - override - _lock_ - _control_ - { - require( - desiredDenorms.length == tokens.length && minimumBalances.length == tokens.length, - "ERR_ARR_LEN" - ); - // This size may not be the same as the input size, as it is possible - // to temporarily exceed the index size while tokens are being phased in - // or out. - uint256 tLen = _tokens.length; - bool[] memory receivedIndices = new bool[](tLen); - // We need to read token records in two separate loops, so - // write them to memory to avoid duplicate storage reads. - Record[] memory records = new Record[](tokens.length); - // Read all the records from storage and mark which of the existing tokens - // were represented in the reindex call. - for (uint256 i = 0; i < tokens.length; i++) { - records[i] = _records[tokens[i]]; - if (records[i].bound) receivedIndices[records[i].index] = true; - } - // If any bound tokens were not sent in this call, set their desired weights to 0. - for (uint256 i = 0; i < tLen; i++) { - if (!receivedIndices[i]) { - _setDesiredDenorm(_tokens[i], 0); - } - } - for (uint256 i = 0; i < tokens.length; i++) { - address token = tokens[i]; - // If an input weight is less than the minimum weight, use that instead. - uint96 denorm = desiredDenorms[i]; - if (denorm < MIN_WEIGHT) denorm = uint96(MIN_WEIGHT); - if (!records[i].bound) { - // If the token is not bound, bind it. - _bind(token, minimumBalances[i], denorm); - } else { - _setDesiredDenorm(token, denorm); - } - } - } - - /** - * @dev Updates the minimum balance for an uninitialized token. - * This becomes useful if a token's external price significantly - * rises after being bound, since the pool can not send a token - * out until it reaches the minimum balance. - */ - function setMinimumBalance( - address token, - uint256 minimumBalance - ) - external - override - _control_ - { - Record storage record = _records[token]; - require(record.bound, "ERR_NOT_BOUND"); - require(!record.ready, "ERR_READY"); - _minimumBalances[token] = minimumBalance; - emit LOG_MINIMUM_BALANCE_UPDATED(token, minimumBalance); - } - -/* ========== Liquidity Provider Actions ========== */ - - /** - * @dev Mint new pool tokens by providing the proportional amount of each - * underlying token's balance relative to the proportion of pool tokens minted. - * - * For any underlying tokens which are not initialized, the caller must provide - * the proportional share of the minimum balance for the token rather than the - * actual balance. - * - * @param poolAmountOut Amount of pool tokens to mint - * @param maxAmountsIn Maximum amount of each token to pay in the same - * order as the pool's _tokens list. - */ - function joinPool(uint256 poolAmountOut, uint256[] calldata maxAmountsIn) - external - override - _lock_ - _public_ - { - uint256 poolTotal = totalSupply(); - uint256 ratio = bdiv(poolAmountOut, poolTotal); - require(ratio != 0, "ERR_MATH_APPROX"); - require(maxAmountsIn.length == _tokens.length, "ERR_ARR_LEN"); - - for (uint256 i = 0; i < maxAmountsIn.length; i++) { - address t = _tokens[i]; - (Record memory record, uint256 realBalance) = _getInputToken(t); - uint256 tokenAmountIn = bmul(ratio, record.balance); - require(tokenAmountIn != 0, "ERR_MATH_APPROX"); - require(tokenAmountIn <= maxAmountsIn[i], "ERR_LIMIT_IN"); - _updateInputToken(t, record, badd(realBalance, tokenAmountIn)); - emit LOG_JOIN(msg.sender, t, tokenAmountIn); - _pullUnderlying(t, msg.sender, tokenAmountIn); - } - _mintPoolShare(poolAmountOut); - _pushPoolShare(msg.sender, poolAmountOut); - } - - /** - * @dev Pay `tokenAmountIn` of `tokenIn` to mint at least `minPoolAmountOut` - * pool tokens. - * - * The pool implicitly swaps `(1- weightTokenIn) * tokenAmountIn` to the other - * underlying tokens. Thus a swap fee is charged against the input tokens. - * - * @param tokenIn Token to send the pool - * @param tokenAmountIn Exact amount of `tokenIn` to pay - * @param minPoolAmountOut Minimum amount of pool tokens to mint - * @return poolAmountOut - Amount of pool tokens minted - */ - function joinswapExternAmountIn( - address tokenIn, - uint256 tokenAmountIn, - uint256 minPoolAmountOut - ) - external - override - _lock_ - _public_ - returns (uint256/* poolAmountOut */) - { - (Record memory inRecord, uint256 realInBalance) = _getInputToken(tokenIn); - - require(tokenAmountIn != 0, "ERR_ZERO_IN"); - - require( - tokenAmountIn <= bmul(inRecord.balance, MAX_IN_RATIO), - "ERR_MAX_IN_RATIO" - ); - - uint256 poolAmountOut = calcPoolOutGivenSingleIn( - inRecord.balance, - inRecord.denorm, - _totalSupply, - _totalWeight, - tokenAmountIn, - _swapFee - ); - - require(poolAmountOut >= minPoolAmountOut, "ERR_LIMIT_OUT"); - - _updateInputToken(tokenIn, inRecord, badd(realInBalance, tokenAmountIn)); - - emit LOG_JOIN(msg.sender, tokenIn, tokenAmountIn); - - _mintPoolShare(poolAmountOut); - _pushPoolShare(msg.sender, poolAmountOut); - _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); - - return poolAmountOut; - } - - /** - * @dev Pay up to `maxAmountIn` of `tokenIn` to mint exactly `poolAmountOut`. - * - * The pool implicitly swaps `(1- weightTokenIn) * tokenAmountIn` to the other - * underlying tokens. Thus a swap fee is charged against the input tokens. - * - * @param tokenIn Token to send the pool - * @param poolAmountOut Exact amount of pool tokens to mint - * @param maxAmountIn Maximum amount of `tokenIn` to pay - * @return tokenAmountIn - Amount of `tokenIn` paid - */ - function joinswapPoolAmountOut( - address tokenIn, - uint256 poolAmountOut, - uint256 maxAmountIn - ) - external - override - _lock_ - _public_ - returns (uint256/* tokenAmountIn */) - { - (Record memory inRecord, uint256 realInBalance) = _getInputToken(tokenIn); - - uint256 tokenAmountIn = calcSingleInGivenPoolOut( - inRecord.balance, - inRecord.denorm, - _totalSupply, - _totalWeight, - poolAmountOut, - _swapFee - ); - - require(tokenAmountIn != 0, "ERR_MATH_APPROX"); - require(tokenAmountIn <= maxAmountIn, "ERR_LIMIT_IN"); - - require( - tokenAmountIn <= bmul(inRecord.balance, MAX_IN_RATIO), - "ERR_MAX_IN_RATIO" - ); - - _updateInputToken(tokenIn, inRecord, badd(realInBalance, tokenAmountIn)); - - emit LOG_JOIN(msg.sender, tokenIn, tokenAmountIn); - - _mintPoolShare(poolAmountOut); - _pushPoolShare(msg.sender, poolAmountOut); - _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); - - return tokenAmountIn; - } - - /** - * @dev Burns `poolAmountIn` pool tokens in exchange for the amounts of each - * underlying token's balance proportional to the ratio of tokens burned to - * total pool supply. The amount of each token transferred to the caller must - * be greater than or equal to the associated minimum output amount from the - * `minAmountsOut` array. - * - * @param poolAmountIn Exact amount of pool tokens to burn - * @param minAmountsOut Minimum amount of each token to receive, in the same - * order as the pool's _tokens list. - */ - function exitPool(uint256 poolAmountIn, uint256[] calldata minAmountsOut) - external - override - _lock_ - { - require(minAmountsOut.length == _tokens.length, "ERR_ARR_LEN"); - uint256 poolTotal = totalSupply(); - uint256 exitFee = bmul(poolAmountIn, EXIT_FEE); - uint256 pAiAfterExitFee = bsub(poolAmountIn, exitFee); - uint256 ratio = bdiv(pAiAfterExitFee, poolTotal); - require(ratio != 0, "ERR_MATH_APPROX"); - - _pullPoolShare(msg.sender, poolAmountIn); - _pushPoolShare(_exitFeeRecipient, exitFee); - _burnPoolShare(pAiAfterExitFee); - for (uint256 i = 0; i < minAmountsOut.length; i++) { - address t = _tokens[i]; - Record memory record = _records[t]; - if (record.ready) { - uint256 tokenAmountOut = bmul(ratio, record.balance); - require(tokenAmountOut != 0, "ERR_MATH_APPROX"); - require(tokenAmountOut >= minAmountsOut[i], "ERR_LIMIT_OUT"); - - _records[t].balance = bsub(record.balance, tokenAmountOut); - emit LOG_EXIT(msg.sender, t, tokenAmountOut); - _pushUnderlying(t, msg.sender, tokenAmountOut); - } else { - // If the token is not initialized, it can not exit the pool. - require(minAmountsOut[i] == 0, "ERR_OUT_NOT_READY"); - } - } - } - - /** - * @dev Burns `poolAmountIn` pool tokens in exchange for at least `minAmountOut` - * of `tokenOut`. Returns the number of tokens sent to the caller. - * - * The pool implicitly burns the tokens for all underlying tokens and swaps them - * to the desired output token. A swap fee is charged against the output tokens. - * - * @param tokenOut Token to receive - * @param poolAmountIn Exact amount of pool tokens to burn - * @param minAmountOut Minimum amount of `tokenOut` to receive - * @return tokenAmountOut - Amount of `tokenOut` received - */ - function exitswapPoolAmountIn( - address tokenOut, - uint256 poolAmountIn, - uint256 minAmountOut - ) - external - override - _lock_ - returns (uint256/* tokenAmountOut */) - { - Record memory outRecord = _getOutputToken(tokenOut); - - uint256 tokenAmountOut = calcSingleOutGivenPoolIn( - outRecord.balance, - outRecord.denorm, - _totalSupply, - _totalWeight, - poolAmountIn, - _swapFee - ); - - require(tokenAmountOut >= minAmountOut, "ERR_LIMIT_OUT"); - - require( - tokenAmountOut <= bmul(outRecord.balance, MAX_OUT_RATIO), - "ERR_MAX_OUT_RATIO" - ); - - _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); - _records[tokenOut].balance = bsub(outRecord.balance, tokenAmountOut); - _decreaseDenorm(outRecord, tokenOut); - uint256 exitFee = bmul(poolAmountIn, EXIT_FEE); - - emit LOG_EXIT(msg.sender, tokenOut, tokenAmountOut); - - _pullPoolShare(msg.sender, poolAmountIn); - _burnPoolShare(bsub(poolAmountIn, exitFee)); - _pushPoolShare(_exitFeeRecipient, exitFee); - - return tokenAmountOut; - } - - /** - * @dev Burn up to `maxPoolAmountIn` for exactly `tokenAmountOut` of `tokenOut`. - * Returns the number of pool tokens burned. - * - * The pool implicitly burns the tokens for all underlying tokens and swaps them - * to the desired output token. A swap fee is charged against the output tokens. - * - * @param tokenOut Token to receive - * @param tokenAmountOut Exact amount of `tokenOut` to receive - * @param maxPoolAmountIn Maximum amount of pool tokens to burn - * @return poolAmountIn - Amount of pool tokens burned - */ - function exitswapExternAmountOut( - address tokenOut, - uint256 tokenAmountOut, - uint256 maxPoolAmountIn - ) - external - override - _lock_ - returns (uint256/* poolAmountIn */) - { - Record memory outRecord = _getOutputToken(tokenOut); - require( - tokenAmountOut <= bmul(outRecord.balance, MAX_OUT_RATIO), - "ERR_MAX_OUT_RATIO" - ); - - uint256 poolAmountIn = calcPoolInGivenSingleOut( - outRecord.balance, - outRecord.denorm, - _totalSupply, - _totalWeight, - tokenAmountOut, - _swapFee - ); - - require(poolAmountIn != 0, "ERR_MATH_APPROX"); - require(poolAmountIn <= maxPoolAmountIn, "ERR_LIMIT_IN"); - - _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); - _records[tokenOut].balance = bsub(outRecord.balance, tokenAmountOut); - _decreaseDenorm(outRecord, tokenOut); - - uint256 exitFee = bmul(poolAmountIn, EXIT_FEE); - - emit LOG_EXIT(msg.sender, tokenOut, tokenAmountOut); - - _pullPoolShare(msg.sender, poolAmountIn); - _burnPoolShare(bsub(poolAmountIn, exitFee)); - _pushPoolShare(_exitFeeRecipient, exitFee); - - return poolAmountIn; - } - -/* ========== Other ========== */ - - /** - * @dev Absorb any tokens that have been sent to the pool. - * If the token is not bound, it will be sent to the unbound - * token handler. - */ - function gulp(address token) external override _lock_ { - Record storage record = _records[token]; - uint256 balance = IERC20(token).balanceOf(address(this)); - if (record.bound) { - if (!record.ready) { - uint256 minimumBalance = _minimumBalances[token]; - if (balance >= minimumBalance) { - _minimumBalances[token] = 0; - record.ready = true; - emit LOG_TOKEN_READY(token); - uint256 additionalBalance = bsub(balance, minimumBalance); - uint256 balRatio = bdiv(additionalBalance, minimumBalance); - uint96 newDenorm = uint96(badd(MIN_WEIGHT, bmul(MIN_WEIGHT, balRatio))); - record.denorm = newDenorm; - record.lastDenormUpdate = uint40(now); - _totalWeight = badd(_totalWeight, newDenorm); - emit LOG_DENORM_UPDATED(token, record.denorm); - } - } - _records[token].balance = balance; - } else { - _pushUnderlying(token, address(_unbindHandler), balance); - _unbindHandler.handleUnbindToken(token, balance); - } - } - -/* ========== Token Swaps ========== */ - - /** - * @dev Execute a token swap with a specified amount of input - * tokens and a minimum amount of output tokens. - * - * Note: Will revert if `tokenOut` is uninitialized. - * - * @param tokenIn Token to swap in - * @param tokenAmountIn Exact amount of `tokenIn` to swap in - * @param tokenOut Token to swap out - * @param minAmountOut Minimum amount of `tokenOut` to receive - * @param maxPrice Maximum ratio of input to output tokens - * @return (tokenAmountOut, spotPriceAfter) - */ - function swapExactAmountIn( - address tokenIn, - uint256 tokenAmountIn, - address tokenOut, - uint256 minAmountOut, - uint256 maxPrice - ) - external - override - _lock_ - _public_ - returns (uint256/* tokenAmountOut */, uint256/* spotPriceAfter */) - { - (Record memory inRecord, uint256 realInBalance) = _getInputToken(tokenIn); - Record memory outRecord = _getOutputToken(tokenOut); - - require( - tokenAmountIn <= bmul(inRecord.balance, MAX_IN_RATIO), - "ERR_MAX_IN_RATIO" - ); - - uint256 spotPriceBefore = calcSpotPrice( - inRecord.balance, - inRecord.denorm, - outRecord.balance, - outRecord.denorm, - _swapFee - ); - require(spotPriceBefore <= maxPrice, "ERR_BAD_LIMIT_PRICE"); - - uint256 tokenAmountOut = calcOutGivenIn( - inRecord.balance, - inRecord.denorm, - outRecord.balance, - outRecord.denorm, - tokenAmountIn, - _swapFee - ); - - require(tokenAmountOut >= minAmountOut, "ERR_LIMIT_OUT"); - - _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); - _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); - - // Update the in-memory record for the spotPriceAfter calculation, - // then update the storage record with the local balance. - outRecord.balance = bsub(outRecord.balance, tokenAmountOut); - _records[tokenOut].balance = outRecord.balance; - // If needed, update the output token's weight. - _decreaseDenorm(outRecord, tokenOut); - - realInBalance = badd(realInBalance, tokenAmountIn); - _updateInputToken(tokenIn, inRecord, realInBalance); - if (inRecord.ready) { - inRecord.balance = realInBalance; - } - - uint256 spotPriceAfter = calcSpotPrice( - inRecord.balance, - inRecord.denorm, - outRecord.balance, - outRecord.denorm, - _swapFee - ); - - require(spotPriceAfter >= spotPriceBefore, "ERR_MATH_APPROX_2"); - require(spotPriceAfter <= maxPrice, "ERR_LIMIT_PRICE"); - require( - spotPriceBefore <= bdiv(tokenAmountIn, tokenAmountOut), - "ERR_MATH_APPROX" - ); - - emit LOG_SWAP(msg.sender, tokenIn, tokenOut, tokenAmountIn, tokenAmountOut); - - return (tokenAmountOut, spotPriceAfter); - } - - /** - * @dev Trades at most `maxAmountIn` of `tokenIn` for exactly `tokenAmountOut` - * of `tokenOut`. - * - * Returns the actual input amount and the new spot price after the swap, - * which can not exceed `maxPrice`. - * - * @param tokenIn Token to swap in - * @param maxAmountIn Maximum amount of `tokenIn` to pay - * @param tokenOut Token to swap out - * @param tokenAmountOut Exact amount of `tokenOut` to receive - * @param maxPrice Maximum ratio of input to output tokens - * @return (tokenAmountIn, spotPriceAfter) - */ - function swapExactAmountOut( - address tokenIn, - uint256 maxAmountIn, - address tokenOut, - uint256 tokenAmountOut, - uint256 maxPrice - ) - external - override - _lock_ - _public_ - returns (uint256 /* tokenAmountIn */, uint256 /* spotPriceAfter */) - { - (Record memory inRecord, uint256 realInBalance) = _getInputToken(tokenIn); - Record memory outRecord = _getOutputToken(tokenOut); - - require( - tokenAmountOut <= bmul(outRecord.balance, MAX_OUT_RATIO), - "ERR_MAX_OUT_RATIO" - ); - - uint256 spotPriceBefore = calcSpotPrice( - inRecord.balance, - inRecord.denorm, - outRecord.balance, - outRecord.denorm, - _swapFee - ); - require(spotPriceBefore <= maxPrice, "ERR_BAD_LIMIT_PRICE"); - - uint256 tokenAmountIn = calcInGivenOut( - inRecord.balance, - inRecord.denorm, - outRecord.balance, - outRecord.denorm, - tokenAmountOut, - _swapFee - ); - - require(tokenAmountIn <= maxAmountIn, "ERR_LIMIT_IN"); - - _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); - _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); - - // Update the in-memory record for the spotPriceAfter calculation, - // then update the storage record with the local balance. - outRecord.balance = bsub(outRecord.balance, tokenAmountOut); - _records[tokenOut].balance = outRecord.balance; - // If needed, update the output token's weight. - _decreaseDenorm(outRecord, tokenOut); - - // Update the balance and (if necessary) weight of the input token. - realInBalance = badd(realInBalance, tokenAmountIn); - _updateInputToken(tokenIn, inRecord, realInBalance); - if (inRecord.ready) { - inRecord.balance = realInBalance; - } - - uint256 spotPriceAfter = calcSpotPrice( - inRecord.balance, - inRecord.denorm, - outRecord.balance, - outRecord.denorm, - _swapFee - ); - - require(spotPriceAfter >= spotPriceBefore, "ERR_MATH_APPROX"); - require(spotPriceAfter <= maxPrice, "ERR_LIMIT_PRICE"); - require( - spotPriceBefore <= bdiv(tokenAmountIn, tokenAmountOut), - "ERR_MATH_APPROX" - ); - - emit LOG_SWAP(msg.sender, tokenIn, tokenOut, tokenAmountIn, tokenAmountOut); - - return (tokenAmountIn, spotPriceAfter); - } - -/* ========== Config Queries ========== */ - /** - * @dev Check if swapping tokens and joining the pool is allowed. - */ - function isPublicSwap() external view override returns (bool) { - return _publicSwap; - } - - function getSwapFee() external view override _viewlock_ returns (uint256/* swapFee */) { - return _swapFee; - } - - function getExitFee() external view override _viewlock_ returns (uint256/* exitFee */) { - return EXIT_FEE; - } - - /** - * @dev Returns the controller address. - */ - function getController() external view override returns (address) - { - return _controller; - } - - /** - * @dev Returns the exit fee recipient address. - */ - function getExitFeeRecipient() external view override returns (address) { - return _exitFeeRecipient; - } - -/* ========== Token Queries ========== */ - - /** - * @dev Check if a token is bound to the pool. - */ - function isBound(address t) external view override returns (bool) { - return _records[t].bound; - } - - /** - * @dev Get the number of tokens bound to the pool. - */ - function getNumTokens() external view override returns (uint256) { - return _tokens.length; - } - - /** - * @dev Get all bound tokens. - */ - function getCurrentTokens() - external - view - override - _viewlock_ - returns (address[] memory tokens) - { - tokens = _tokens; - } - - /** - * @dev Returns the list of tokens which have a desired weight above 0. - * Tokens with a desired weight of 0 are set to be phased out of the pool. - */ - function getCurrentDesiredTokens() - external - view - override - _viewlock_ - returns (address[] memory tokens) - { - address[] memory tempTokens = _tokens; - tokens = new address[](tempTokens.length); - uint256 usedIndex = 0; - for (uint256 i = 0; i < tokens.length; i++) { - address token = tempTokens[i]; - if (_records[token].desiredDenorm > 0) { - tokens[usedIndex++] = token; - } - } - assembly { mstore(tokens, usedIndex) } - } - - /** - * @dev Returns the denormalized weight of a bound token. - */ - function getDenormalizedWeight(address token) - external - view - override - _viewlock_ - returns (uint256/* denorm */) - { - require(_records[token].bound, "ERR_NOT_BOUND"); - return _records[token].denorm; - } - - /** - * @dev Returns the record for a token bound to the pool. - */ - function getTokenRecord(address token) - external - view - override - _viewlock_ - returns (Record memory record) - { - record = _records[token]; - require(record.bound, "ERR_NOT_BOUND"); - } - - /** - * @dev Finds the first token which is both initialized and has a - * desired weight above 0, then returns the address of that token - * and the extrapolated value of the pool in terms of that token. - * - * The value is extrapolated by multiplying the token's - * balance by the reciprocal of its normalized weight. - * @return (token, extrapolatedValue) - */ - function extrapolatePoolValueFromToken() - external - view - override - _viewlock_ - returns (address/* token */, uint256/* extrapolatedValue */) - { - address token; - uint256 extrapolatedValue; - uint256 len = _tokens.length; - for (uint256 i = 0; i < len; i++) { - token = _tokens[i]; - Record storage record = _records[token]; - if (record.ready && record.desiredDenorm > 0) { - extrapolatedValue = bmul( - record.balance, - bdiv(_totalWeight, record.denorm) - ); - break; - } - } - require(extrapolatedValue > 0, "ERR_NONE_READY"); - return (token, extrapolatedValue); - } - - /** - * @dev Get the total denormalized weight of the pool. - */ - function getTotalDenormalizedWeight() - external - view - override - _viewlock_ - returns (uint256) - { - return _totalWeight; - } - - /** - * @dev Returns the stored balance of a bound token. - */ - function getBalance(address token) external view override _viewlock_ returns (uint256) { - Record storage record = _records[token]; - require(record.bound, "ERR_NOT_BOUND"); - return record.balance; - } - - /** - * @dev Get the minimum balance of an uninitialized token. - * Note: Throws if the token is initialized. - */ - function getMinimumBalance(address token) external view override _viewlock_ returns (uint256) { - Record memory record = _records[token]; - require(record.bound, "ERR_NOT_BOUND"); - require(!record.ready, "ERR_READY"); - return _minimumBalances[token]; - } - - /** - * @dev Returns the balance of a token which is used in price - * calculations. If the token is initialized, this is the - * stored balance; if not, this is the minimum balance. - */ - function getUsedBalance(address token) external view override _viewlock_ returns (uint256) { - Record memory record = _records[token]; - require(record.bound, "ERR_NOT_BOUND"); - if (!record.ready) { - return _minimumBalances[token]; - } - return record.balance; - } - -/* ========== Price Queries ========== */ - /** - * @dev Returns the spot price for `tokenOut` in terms of `tokenIn`. - */ - function getSpotPrice(address tokenIn, address tokenOut) - external - view - override - _viewlock_ - returns (uint256) - { - (Record memory inRecord,) = _getInputToken(tokenIn); - Record memory outRecord = _getOutputToken(tokenOut); - return - calcSpotPrice( - inRecord.balance, - inRecord.denorm, - outRecord.balance, - outRecord.denorm, - _swapFee - ); - } - -/* ========== Pool Share Internal Functions ========== */ - - function _pullPoolShare(address from, uint256 amount) internal { - _pull(from, amount); - } - - function _pushPoolShare(address to, uint256 amount) internal { - _push(to, amount); - } - - function _mintPoolShare(uint256 amount) internal { - _mint(amount); - } - - function _burnPoolShare(uint256 amount) internal { - _burn(amount); - } - -/* ========== Underlying Token Internal Functions ========== */ - // 'Underlying' token-manipulation functions make external calls but are NOT locked - // You must `_lock_` or otherwise ensure reentry-safety - - function _pullUnderlying( - address erc20, - address from, - uint256 amount - ) internal { - (bool success, bytes memory data) = erc20.call( - abi.encodeWithSelector( - IERC20.transferFrom.selector, - from, - address(this), - amount - ) - ); - require( - success && (data.length == 0 || abi.decode(data, (bool))), - "ERR_ERC20_FALSE" - ); - } - - function _pushUnderlying( - address erc20, - address to, - uint256 amount - ) internal { - (bool success, bytes memory data) = erc20.call( - abi.encodeWithSelector( - IERC20.transfer.selector, - to, - amount - ) - ); - require( - success && (data.length == 0 || abi.decode(data, (bool))), - "ERR_ERC20_FALSE" - ); - } - -/* ========== Token Management Internal Functions ========== */ - - /** - * @dev Bind a token by address without actually depositing a balance. - * The token will be unable to be swapped out until it reaches the minimum balance. - * Note: Token must not already be bound. - * Note: `minimumBalance` should represent an amount of the token which is worth - * the portion of the current pool value represented by the minimum weight. - * @param token Address of the token to bind - * @param minimumBalance minimum balance to reach before the token can be swapped out - * @param desiredDenorm Desired weight for the token. - */ - function _bind( - address token, - uint256 minimumBalance, - uint96 desiredDenorm - ) internal { - require(!_records[token].bound, "ERR_IS_BOUND"); - - require(desiredDenorm >= MIN_WEIGHT, "ERR_MIN_WEIGHT"); - require(desiredDenorm <= MAX_WEIGHT, "ERR_MAX_WEIGHT"); - require(minimumBalance >= MIN_BALANCE, "ERR_MIN_BALANCE"); - - _records[token] = Record({ - bound: true, - ready: false, - lastDenormUpdate: 0, - denorm: 0, - desiredDenorm: desiredDenorm, - index: uint8(_tokens.length), - balance: 0 - }); - _tokens.push(token); - _minimumBalances[token] = minimumBalance; - emit LOG_TOKEN_ADDED(token, desiredDenorm, minimumBalance); - } - - /** - * @dev Remove a token from the pool. - * Replaces the address in the tokens array with the last address, - * then removes it from the array. - * Note: This should only be called after the total weight has been adjusted. - * Note: Must be called in a function with: - * - _lock_ modifier to prevent reentrance - * - requirement that the token is bound - */ - function _unbind(address token) internal { - Record memory record = _records[token]; - uint256 tokenBalance = record.balance; - - // Swap the token-to-unbind with the last token, - // then delete the last token - uint256 index = record.index; - uint256 last = _tokens.length - 1; - // Only swap the token with the last token if it is not - // already at the end of the array. - if (index != last) { - _tokens[index] = _tokens[last]; - _records[_tokens[index]].index = uint8(index); - } - _tokens.pop(); - _records[token] = Record({ - bound: false, - ready: false, - lastDenormUpdate: 0, - denorm: 0, - desiredDenorm: 0, - index: 0, - balance: 0 - }); - // transfer any remaining tokens out - _pushUnderlying(token, address(_unbindHandler), tokenBalance); - _unbindHandler.handleUnbindToken(token, tokenBalance); - emit LOG_TOKEN_REMOVED(token); - } - - function _setDesiredDenorm(address token, uint96 desiredDenorm) internal { - Record storage record = _records[token]; - require(record.bound, "ERR_NOT_BOUND"); - // If the desired weight is 0, this will trigger a gradual unbinding of the token. - // Therefore the weight only needs to be above the minimum weight if it isn't 0. - require( - desiredDenorm >= MIN_WEIGHT || desiredDenorm == 0, - "ERR_MIN_WEIGHT" - ); - require(desiredDenorm <= MAX_WEIGHT, "ERR_MAX_WEIGHT"); - record.desiredDenorm = desiredDenorm; - emit LOG_DESIRED_DENORM_SET(token, desiredDenorm); - } - - function _increaseDenorm(Record memory record, address token) internal { - // If the weight does not need to increase or the token is not - // initialized, don't do anything. - if ( - record.denorm >= record.desiredDenorm || - !record.ready || - now - record.lastDenormUpdate < WEIGHT_UPDATE_DELAY - ) return; - uint96 oldWeight = record.denorm; - uint96 denorm = record.desiredDenorm; - uint256 maxDiff = bmul(oldWeight, WEIGHT_CHANGE_PCT); - uint256 diff = bsub(denorm, oldWeight); - if (diff > maxDiff) { - denorm = uint96(badd(oldWeight, maxDiff)); - diff = maxDiff; - } - // If new total weight exceeds the maximum, do not update - uint256 newTotalWeight = badd(_totalWeight, diff); - if (newTotalWeight > MAX_TOTAL_WEIGHT) return; - _totalWeight = newTotalWeight; - // Update the in-memory denorm value for spot-price computations. - record.denorm = denorm; - // Update the storage record - _records[token].denorm = denorm; - _records[token].lastDenormUpdate = uint40(now); - emit LOG_DENORM_UPDATED(token, denorm); - } - - function _decreaseDenorm(Record memory record, address token) internal { - // If the weight does not need to decrease, don't do anything. - if ( - record.denorm <= record.desiredDenorm || - !record.ready || - now - record.lastDenormUpdate < WEIGHT_UPDATE_DELAY - ) return; - uint96 oldWeight = record.denorm; - uint96 denorm = record.desiredDenorm; - uint256 maxDiff = bmul(oldWeight, WEIGHT_CHANGE_PCT); - uint256 diff = bsub(oldWeight, denorm); - if (diff > maxDiff) { - denorm = uint96(bsub(oldWeight, maxDiff)); - diff = maxDiff; - } - if (denorm <= MIN_WEIGHT) { - denorm = 0; - _totalWeight = bsub(_totalWeight, denorm); - // Because this is removing the token from the pool, the - // in-memory denorm value is irrelevant, as it is only used - // to calculate the new spot price, but the spot price calc - // will throw if it is passed 0 for the denorm. - _unbind(token); - } else { - _totalWeight = bsub(_totalWeight, diff); - // Update the in-memory denorm value for spot-price computations. - record.denorm = denorm; - // Update the stored denorm value - _records[token].denorm = denorm; - _records[token].lastDenormUpdate = uint40(now); - emit LOG_DENORM_UPDATED(token, denorm); - } - } - - /** - * @dev Handles weight changes and initialization of an - * input token. - * - * If the token is not initialized and the new balance is - * still below the minimum, this will not do anything. - * - * If the token is not initialized but the new balance will - * bring the token above the minimum balance, this will - * mark the token as initialized, remove the minimum - * balance and set the weight to the minimum weight plus - * 1%. - * - * - * @param token Address of the input token - * @param record Token record with minimums applied to the balance - * and weight if the token was uninitialized. - */ - function _updateInputToken( - address token, - Record memory record, - uint256 realBalance - ) - internal - { - if (!record.ready) { - // Check if the minimum balance has been reached - if (realBalance >= record.balance) { - // Remove the minimum balance record - _minimumBalances[token] = 0; - // Mark the token as initialized - _records[token].ready = true; - record.ready = true; - emit LOG_TOKEN_READY(token); - // Set the initial denorm value to the minimum weight times one plus - // the ratio of the increase in balance over the minimum to the minimum - // balance. - // weight = (1 + ((bal - min_bal) / min_bal)) * min_weight - uint256 additionalBalance = bsub(realBalance, record.balance); - uint256 balRatio = bdiv(additionalBalance, record.balance); - record.denorm = uint96(badd(MIN_WEIGHT, bmul(MIN_WEIGHT, balRatio))); - _records[token].denorm = record.denorm; - _records[token].lastDenormUpdate = uint40(now); - _totalWeight = badd(_totalWeight, record.denorm); - emit LOG_DENORM_UPDATED(token, record.denorm); - } else { - uint256 realToMinRatio = bdiv( - bsub(record.balance, realBalance), - record.balance - ); - uint256 weightPremium = bmul(MIN_WEIGHT / 10, realToMinRatio); - record.denorm = uint96(badd(MIN_WEIGHT, weightPremium)); - } - // If the token is still not ready, do not adjust the weight. - } else { - // If the token is already initialized, update the weight (if any adjustment - // is needed). - _increaseDenorm(record, token); - } - // Regardless of whether the token is initialized, store the actual new balance. - _records[token].balance = realBalance; - } - -/* ========== Token Query Internal Functions ========== */ - - /** - * @dev Get the record for a token which is being swapped in. - * The token must be bound to the pool. If the token is not - * initialized (meaning it does not have the minimum balance) - * this function will return the actual balance of the token - * which the pool holds, but set the record's balance and weight - * to the token's minimum balance and the pool's minimum weight. - * This allows the token swap to be priced correctly even if the - * pool does not own any of the tokens. - */ - function _getInputToken(address token) - internal - view - returns (Record memory record, uint256 realBalance) - { - record = _records[token]; - require(record.bound, "ERR_NOT_BOUND"); - - realBalance = record.balance; - // If the input token is not initialized, we use the minimum - // initial weight and minimum initial balance instead of the - // real values for price and output calculations. - if (!record.ready) { - record.balance = _minimumBalances[token]; - uint256 realToMinRatio = bdiv( - bsub(record.balance, realBalance), - record.balance - ); - uint256 weightPremium = bmul(MIN_WEIGHT / 10, realToMinRatio); - record.denorm = uint96(badd(MIN_WEIGHT, weightPremium)); - } - } - - function _getOutputToken(address token) - internal - view - returns (Record memory record) - { - record = _records[token]; - require(record.bound, "ERR_NOT_BOUND"); - // Tokens which have not reached their minimum balance can not be - // swapped out. - require(record.ready, "ERR_OUT_NOT_READY"); - } -} - - -interface TokenUnbindHandler { - /** - * @dev Receive `amount` of `token` from the pool. - */ - function handleUnbindToken(address token, uint256 amount) external; -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/SafeMath.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/SafeMath.sol deleted file mode 100644 index b644454dd5..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/SafeMath.sol +++ /dev/null @@ -1,159 +0,0 @@ -// 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; - } -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/Safemoon.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/Safemoon.sol deleted file mode 100644 index 74cdc8fddd..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/Safemoon.sol +++ /dev/null @@ -1,1166 +0,0 @@ -/** - *Submitted for verification at BscScan.com on 2021-03-01 -*/ - -/** - *Submitted for verification at BscScan.com on 2021-03-01 -*/ - -/** - - #BEE - - #LIQ+#RFI+#SHIB+#DOGE = #BEE - - #SAFEMOON features: - 3% fee auto add to the liquidity pool to locked forever when selling - 2% fee auto distribute to all holders - I created a black hole so #Bee token will deflate itself in supply with every transaction - 50% Supply is burned at start. - - - */ - -pragma solidity ^0.6.11; -// SPDX-License-Identifier: Unlicensed -interface IERC20 { - - 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; - } -} - -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; - } -} - - -/** - * @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) { - // 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); - } - - /** - * @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); - } - } - } -} - -/** - * @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; - address private _previousOwner; - uint256 private _lockTime; - - 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 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; - } - - function geUnlockTime() public view returns (uint256) { - return _lockTime; - } - - //Locks the contract for owner for the amount of time provided - function lock(uint256 time) public virtual onlyOwner { - _previousOwner = _owner; - _owner = address(0); - _lockTime = now + time; - emit OwnershipTransferred(_owner, address(0)); - } - - //Unlocks the contract for owner when _lockTime is exceeds - function unlock() public virtual { - require(_previousOwner == msg.sender, "You don't have permission to unlock"); - require(now > _lockTime , "Contract is locked until 7 days"); - emit OwnershipTransferred(_owner, _previousOwner); - _owner = _previousOwner; - } -} - -// pragma solidity >=0.5.0; - -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; -} - - -// pragma solidity >=0.5.0; - -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; -} - -// 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 SafeMoon is Context, IERC20, Ownable { - using SafeMath for uint256; - using Address for address; - - 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; - - uint256 private constant MAX = ~uint256(0); - uint256 private _tTotal = 1000000000 * 10**6 * 10**9; - uint256 private _rTotal = (MAX - (MAX % _tTotal)); - uint256 private _tFeeTotal; - - string private _name = "SafeMoon"; - string private _symbol = "SAFEMOON"; - uint8 private _decimals = 9; - - uint256 public _taxFee = 5; - uint256 private _previousTaxFee = _taxFee; - - uint256 public _liquidityFee = 5; - uint256 private _previousLiquidityFee = _liquidityFee; - - IUniswapV2Router02 public immutable uniswapV2Router; - address public immutable uniswapV2Pair; - - bool inSwapAndLiquify; - bool public swapAndLiquifyEnabled = true; - - uint256 public _maxTxAmount = 5000000 * 10**6 * 10**9; - uint256 private numTokensSellToAddToLiquidity = 500000 * 10**6 * 10**9; - - event MinTokensBeforeSwapUpdated(uint256 minTokensBeforeSwap); - 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(0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F); - // Create a uniswap pair for this new token - uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()) - .createPair(address(this), _uniswapV2Router.WETH()); - - // set the rest of the contract variables - uniswapV2Router = _uniswapV2Router; - - //exclude owner and this contract from fee - _isExcludedFromFee[owner()] = true; - _isExcludedFromFee[address(this)] = true; - - emit Transfer(address(0), _msgSender(), _tTotal); - } - - 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; - } - - function totalSupply() public 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) public override returns (bool) { - _transfer(_msgSender(), recipient, amount); - return true; - } - - function allowance(address owner, address spender) public view override returns (uint256) { - return _allowances[owner][spender]; - } - - function approve(address spender, uint256 amount) public override returns (bool) { - _approve(_msgSender(), spender, amount); - return true; - } - - function transferFrom(address sender, address recipient, uint256 amount) public 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 isExcludedFromReward(address account) public view returns (bool) { - return _isExcluded[account]; - } - - function totalFees() public view returns (uint256) { - return _tFeeTotal; - } - - function deliver(uint256 tAmount) public { - 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) public 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) public 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 _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 excludeFromFee(address account) public onlyOwner { - _isExcludedFromFee[account] = true; - } - - function includeInFee(address account) public onlyOwner { - _isExcludedFromFee[account] = false; - } - - function setTaxFeePercent(uint256 taxFee) external onlyOwner() { - _taxFee = taxFee; - } - - function setLiquidityFeePercent(uint256 liquidityFee) external onlyOwner() { - _liquidityFee = liquidityFee; - } - - function setMaxTxPercent(uint256 maxTxPercent) external onlyOwner() { - _maxTxAmount = _tTotal.mul(maxTxPercent).div( - 10**2 - ); - } - - function setSwapAndLiquifyEnabled(bool _enabled) public onlyOwner { - swapAndLiquifyEnabled = _enabled; - emit SwapAndLiquifyEnabledUpdated(_enabled); - } - - //to recieve ETH from uniswapV2Router when swaping - receive() external payable {} - - 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) public view returns(bool) { - return _isExcludedFromFee[account]; - } - - 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."); - - // is the token balance of this contract address over the min number of - // tokens that we need to initiate a swap + liquidity lock? - // also, don't get caught in a circular liquidity event. - // also, don't swap & liquify if sender is uniswap pair. - uint256 contractTokenBalance = balanceOf(address(this)); - - if(contractTokenBalance >= _maxTxAmount) - { - contractTokenBalance = _maxTxAmount; - } - - bool overMinTokenBalance = contractTokenBalance >= numTokensSellToAddToLiquidity; - if ( - overMinTokenBalance && - !inSwapAndLiquify && - from != uniswapV2Pair && - swapAndLiquifyEnabled - ) { - contractTokenBalance = numTokensSellToAddToLiquidity; - //add liquidity - swapAndLiquify(contractTokenBalance); - } - - //indicates if fee should be deducted from transfer - bool takeFee = true; - - //if any account belongs to _isExcludedFromFee account then remove the fee - if(_isExcludedFromFee[from] || _isExcludedFromFee[to]){ - takeFee = false; - } - - //transfer amount, it will take tax, burn, liquidity fee - _tokenTransfer(from,to,amount,takeFee); - } - - function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap { - // split the contract balance into halves - uint256 half = contractTokenBalance.div(2); - uint256 otherHalf = contractTokenBalance.sub(half); - - // capture the contract's current ETH balance. - // this is so that we can capture exactly the amount of ETH that the - // swap creates, and not make the liquidity event include any ETH that - // has been manually sent to the contract - uint256 initialBalance = address(this).balance; - - // swap tokens for ETH - swapTokensForEth(half); // <- this breaks the ETH -> HATE swap when swap+liquify is triggered - - // how much ETH did we just swap into? - uint256 newBalance = address(this).balance.sub(initialBalance); - - // add liquidity to uniswap - addLiquidity(otherHalf, newBalance); - - emit SwapAndLiquify(half, newBalance, otherHalf); - } - - 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), - block.timestamp - ); - } - - function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private { - // approve token transfer to cover all possible scenarios - _approve(address(this), address(uniswapV2Router), tokenAmount); - - // add the liquidity - uniswapV2Router.addLiquidityETH{value: ethAmount}( - address(this), - tokenAmount, - 0, // slippage is unavoidable - 0, // slippage is unavoidable - owner(), - block.timestamp - ); - } - - //this method is responsible for taking all fee, if takeFee is true - function _tokenTransfer(address sender, address recipient, uint256 amount,bool takeFee) private { - 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); - } - - - - -} diff --git a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/SpaceGoat.sol b/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/SpaceGoat.sol deleted file mode 100644 index b204542e2a..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-high/0.6.11/SpaceGoat.sol +++ /dev/null @@ -1,1198 +0,0 @@ -/** - *Submitted for verification at BscScan.com on 2021-05-21 -*/ - -pragma solidity ^0.6.11; -// SPDX-License-Identifier: Unlicensed -interface IERC20 { - - 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; - } -} - -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; - } -} - - -/** - * @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) { - // 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); - } - - /** - * @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); - } - } - } -} - -/** - * @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; - address private _previousOwner; - - 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 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; - } -} - - -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 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 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; -} - -contract SpaceGoat is Context, IERC20, Ownable { - using SafeMath for uint256; - using Address for address; - address contractOwner; - - 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; - - uint256 private MAX = ~uint256(0); - uint256 private constant _tTotal = 1000000000 * 10**6 * 10**9; - uint256 private _rTotal = (MAX - (MAX % _tTotal)); - uint256 private _tFeeTotal; - - string private constant _name = "SPACEGOAT TOKEN"; - string private constant _symbol = "SGT"; - uint8 private constant _decimals = 9; - - uint256 public _taxFee = 4; //4% holder distribution - uint256 private _previousTaxFee = _taxFee; - - uint256 public _liquidityFee = 2; //2% liquidity pool - uint256 private _previousLiquidityFee = _liquidityFee; - - uint256 public _charityPlusExmarketFee = 4; //1% charity fee + 1% exMarket fee + 2% burn wallet - uint256 private _previouscharityPlusExmarketFee = _charityPlusExmarketFee; - - address payable private _charityWallet = 0xD42e4F076721b92f1B87E4A0B3d0bAa354b2a7d8; // Charity Fund - address payable private _exMarketWallet = 0x8fc0b3DC7690a3CD96a0033B48073ACa7c859713; // Exchange + Marketing Fund - address payable private _burnWallet = 0x0000000000000000000000000000000000000001; - - IUniswapV2Router02 public uniswapV2Router; - address public uniswapV2Pair; - address private pancake = 0x10ED43C718714eb63d5aA57B78B54704E256024E; - - bool inSwapAndLiquify; - bool public swapAndLiquifyEnabled = false; - bool public burnEnabled = true; - - uint256 public _maxTxAmount = 5000000 * 10**6 * 10**9; - uint256 private constant numTokensSellToAddToLiquidity = 500000 * 10**6 * 10**9; - - event MinTokensBeforeSwapUpdated(uint256 minTokensBeforeSwap); - event SwapAndLiquifyEnabledUpdated(bool enabled); - event BurnEnabledUpdated(bool enabled); - event SwapAndLiquify( - uint256 tokensSwapped, - uint256 ethReceived, - uint256 tokensIntoLiquidity - ); - - modifier lockTheSwap { - inSwapAndLiquify = true; - _; - inSwapAndLiquify = false; - } - - constructor () public { - _rOwned[_msgSender()] = _rTotal; - - setRouterAddress(pancake); - //exclude owner and this contract from fee - _isExcludedFromFee[owner()] = true; - _isExcludedFromFee[address(this)] = true; - - contractOwner = msg.sender; - - emit Transfer(address(0), _msgSender(), _tTotal); - } - - function name() public pure returns (string memory) { - return _name; - } - - function symbol() public pure returns (string memory) { - return _symbol; - } - - - function decimals() public pure returns (uint8) { - return _decimals; - } - - function totalSupply() public 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) public override returns (bool) { - _transfer(_msgSender(), recipient, amount); - return true; - } - - function allowance(address owner, address spender) public view override returns (uint256) { - return _allowances[owner][spender]; - } - - function approve(address spender, uint256 amount) public override returns (bool) { - _approve(_msgSender(), spender, amount); - return true; - } - - function transferFrom(address sender, address recipient, uint256 amount) public 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 isExcludedFromReward(address account) public view returns (bool) { - return _isExcluded[account]; - } - - function totalFees() public view returns (uint256) { - return _tFeeTotal; - } - - function deliver(uint256 tAmount) public onlyOwner{ - 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) public 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) public onlyOwner() { - require(!_isExcluded[account], "Account is not 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 not 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 _transferBothExcluded(address sender, address recipient, uint256 tAmount) private { - (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity, uint tcharityPlusExmarketFee) = _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); - _takecharityPlusExmarketFee(tcharityPlusExmarketFee); - emit Transfer(sender, recipient, tTransferAmount); - } - - function excludeFromFee(address account) public onlyOwner { - _isExcludedFromFee[account] = true; - } - - function includeInFee(address account) public onlyOwner { - _isExcludedFromFee[account] = false; - } - - function setTaxFeePercent(uint256 taxFee) external onlyOwner() { - _taxFee = taxFee; - } - - function setLiquidityFeePercent(uint256 liquidityFee) external onlyOwner() { - _liquidityFee = liquidityFee; - } - - function setRouterAddress(address newRouter) public onlyOwner() { - //Thank you FreezyEx - // current v2 = 0x10ED43C718714eb63d5aA57B78B54704E256024E - // Address the version change problem PancakeSwap - IUniswapV2Router02 _newPancakeRouter = IUniswapV2Router02(newRouter); - uniswapV2Pair = IUniswapV2Factory(_newPancakeRouter.factory()).createPair(address(this), _newPancakeRouter.WETH()); - uniswapV2Router = _newPancakeRouter; - } - - function setcharityPlusExmarketFee(uint256 charityPlusExmarketFee) external onlyOwner() { - _charityPlusExmarketFee = charityPlusExmarketFee; - } - - function setCharityWallet(address payable charityWalletAddress) external onlyOwner() { - _charityWallet = charityWalletAddress; - } - - function setExMarketWallet(address payable exMarketWalletAddress) external onlyOwner() { - _exMarketWallet = exMarketWalletAddress; - } - - function setBurnEnabled(bool _enabled) public onlyOwner { - burnEnabled = _enabled; - emit BurnEnabledUpdated(_enabled); - } - - function setMaxTxPercent(uint256 maxTxPercent) external onlyOwner() { - _maxTxAmount = _tTotal.mul(maxTxPercent).div( - 10**2 - ); - } - - function setSwapAndLiquifyEnabled(bool _enabled) public onlyOwner { - swapAndLiquifyEnabled = _enabled; - emit SwapAndLiquifyEnabledUpdated(_enabled); - } - - //to receive BNB from uniswapV2Router when swapping - receive() external payable {} - - 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) { - (uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity, uint256 tcharityPlusExmarketFee) = _getTValues(tAmount); - (uint256 rAmount, uint256 rTransferAmount, uint256 rFee) = _getRValues(tAmount, tFee, tLiquidity, tcharityPlusExmarketFee, _getRate()); - return (rAmount, rTransferAmount, rFee, tTransferAmount, tFee, tLiquidity, tcharityPlusExmarketFee); - } - - function _getTValues(uint256 tAmount) private view returns (uint256, uint256, uint256, uint256) { - uint256 tFee = calculateTaxFee(tAmount); - uint256 tLiquidity = calculateLiquidityFee(tAmount); - uint tcharityPlusExmarketFee; - if(msg.sender != contractOwner){ - tcharityPlusExmarketFee = calculateCharityPlusExmarketFee(tAmount); - }else{ - tcharityPlusExmarketFee=0; - } - uint256 tTransferAmount = tAmount.sub(tFee).sub(tLiquidity).sub(tcharityPlusExmarketFee); - return (tTransferAmount, tFee, tLiquidity, tcharityPlusExmarketFee); - } - - function _getRValues(uint256 tAmount, uint256 tFee, uint256 tLiquidity, uint256 tcharityPlusExmarketFee, uint256 currentRate) private view returns (uint256, uint256, uint256) { - uint256 rAmount = tAmount.mul(currentRate); - uint256 rFee = tFee.mul(currentRate); - uint256 rLiquidity = tLiquidity.mul(currentRate); - uint256 rcharityPlusExmarketFee; - - if(msg.sender != contractOwner){ - rcharityPlusExmarketFee = tcharityPlusExmarketFee.mul(currentRate); - }else{ - rcharityPlusExmarketFee=0; - } - - uint256 rTransferAmount = rAmount.sub(rFee).sub(rLiquidity).sub(rcharityPlusExmarketFee); - 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 _takecharityPlusExmarketFee(uint256 tcharityPlusExmarketFee) private{ - - uint256 currentRate = _getRate(); - uint256 rCharityFee = tcharityPlusExmarketFee.mul(currentRate); - uint256 burnWallet; - if (burnEnabled) { - //we want to stop splitting fees - burnWallet = rCharityFee.div(2); - } - - uint256 charity = rCharityFee.sub(burnWallet); - uint256 charityFee = charity.div(2); - uint256 exMarketFee = charity.sub(charityFee); - - - _rOwned[_charityWallet] = _rOwned[_charityWallet].add(charityFee); - _rOwned[_exMarketWallet] = _rOwned[_exMarketWallet].add(exMarketFee); - _rOwned[_burnWallet] = _rOwned[_burnWallet].add(burnWallet); - - if(_isExcluded[address(this)]) - _tOwned[address(this)] = _tOwned[address(this)].add(tcharityPlusExmarketFee); - } - - 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 calculateCharityPlusExmarketFee(uint256 _amount) private view returns (uint256) { - return _amount.mul(_charityPlusExmarketFee).div( - 10**2 - ); - } - - function removeAllFee() private { - if(_taxFee == 0 && _liquidityFee == 0 && _charityPlusExmarketFee == 0) return; - - _previousTaxFee = _taxFee; - _previousLiquidityFee = _liquidityFee; - _previouscharityPlusExmarketFee = _charityPlusExmarketFee; - - _taxFee = 0; - _liquidityFee = 0; - _charityPlusExmarketFee=0; - } - - function restoreAllFee() private { - _taxFee = _previousTaxFee; - _liquidityFee = _previousLiquidityFee; - _charityPlusExmarketFee = _previouscharityPlusExmarketFee; - } - - function isExcludedFromFee(address account) public view returns(bool) { - return _isExcludedFromFee[account]; - } - - 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."); - - // is the token balance of this contract address over the min number of - // tokens that we need to initiate a swap + liquidity lock? - // also, don't get caught in a circular liquidity event. - // also, don't swap & liquify if sender is uniswap pair. - uint256 contractTokenBalance = balanceOf(address(this)); - - if(contractTokenBalance >= _maxTxAmount) - { - contractTokenBalance = _maxTxAmount; - } - - bool overMinTokenBalance = contractTokenBalance >= numTokensSellToAddToLiquidity; - - if ( - overMinTokenBalance && - !inSwapAndLiquify && - from != uniswapV2Pair && - swapAndLiquifyEnabled - ) { - contractTokenBalance = numTokensSellToAddToLiquidity; - //add liquidity - swapAndLiquify(contractTokenBalance); - } - - //indicates if fee should be deducted from transfer - bool takeFee = true; - - //if any account belongs to _isExcludedFromFee account then remove the fee - if(_isExcludedFromFee[from] || _isExcludedFromFee[to]){ - takeFee = false; - } - - //transfer amount, it will take tax, burn, liquidity fee - _tokenTransfer(from,to,amount,takeFee); - - } - - function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap { - // split the contract balance into halves - uint256 half = contractTokenBalance.div(2); - uint256 otherHalf = contractTokenBalance.sub(half); - - uint256 initialBalance = address(this).balance; - - // swap tokens for BNB - swapTokensForBNB(half); - - // how much BNB did we just swap into? - uint256 newBalance = address(this).balance.sub(initialBalance); - - // add liquidity to pancakeswap - addLiquidity(otherHalf, newBalance); - - emit SwapAndLiquify(half, newBalance, otherHalf); - } - - - function swapTokensForBNB(uint256 tokenAmount) private { - // generate the pancakeswap pair path of token -> BNB - 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 BNB - path, - address(this), - block.timestamp - ); - } - - function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private returns(uint){ - // approve token transfer to cover all possible scenarios - _approve(address(this), address(uniswapV2Router), tokenAmount); - - // add the liquidity - uniswapV2Router.addLiquidityETH{value: ethAmount}( - address(this), - tokenAmount, - 0, // slippage is unavoidable - 0, // slippage is unavoidable - owner(), - block.timestamp - ); - return tokenAmount; - } - - //this method is responsible for taking all fee, if takeFee is true - function _tokenTransfer(address sender, address recipient, uint256 amount,bool takeFee) private { - 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]) { - _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, uint256 tcharityPlusExmarketFee) = _getValues(tAmount); - _rOwned[sender] = _rOwned[sender].sub(rAmount); - _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount); - _takeLiquidity(tLiquidity); - _reflectFee(rFee, tFee); - _takecharityPlusExmarketFee(tcharityPlusExmarketFee); - - 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,uint256 tcharityPlusExmarketFee) = _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); - _takecharityPlusExmarketFee(tcharityPlusExmarketFee); - - 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,uint256 tcharityPlusExmarketFee) = _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); - _takecharityPlusExmarketFee(tcharityPlusExmarketFee); - - emit Transfer(sender, recipient, tTransferAmount); - } - -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BConst.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BConst.sol deleted file mode 100644 index 7f2b3f25d3..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BConst.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.6.0; - - -/************************************************************************************************ -Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BConst.sol - -This source code has been modified from the original, which was copied from the github repository -at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. - -Subject to the GPL-3.0 license -*************************************************************************************************/ - - -contract BConst { - uint256 public constant VERSION_NUMBER = 1; - -/* --- Weight Updates --- */ - - // Minimum time passed between each weight update for a token. - uint256 internal constant WEIGHT_UPDATE_DELAY = 30 minutes; - - // Maximum percent by which a weight can adjust at a time - // relative to the current weight. - // The number of iterations needed to move from weight A to weight B is the floor of: - // (A > B): (ln(A) - ln(B)) / ln(1.01) - // (B > A): (ln(A) - ln(B)) / ln(0.99) - uint256 internal constant WEIGHT_CHANGE_PCT = BONE/100; - - uint256 internal constant BONE = 10**18; - - uint256 internal constant MIN_BOUND_TOKENS = 2; - uint256 internal constant MAX_BOUND_TOKENS = 10; - - // Minimum swap fee. - uint256 internal constant MIN_FEE = BONE / 10**6; - // Maximum swap or exit fee. - uint256 internal constant MAX_FEE = BONE / 10; - // Actual exit fee. - uint256 internal constant EXIT_FEE = 5e15; - - // Default total of all desired weights. Can differ by up to BONE. - uint256 internal constant DEFAULT_TOTAL_WEIGHT = BONE * 25; - // Minimum weight for any token (1/100). - uint256 internal constant MIN_WEIGHT = BONE / 4; - uint256 internal constant MAX_WEIGHT = BONE * 25; - // Maximum total weight. - uint256 internal constant MAX_TOTAL_WEIGHT = BONE * 27; - // Minimum balance for a token (only applied at initialization) - uint256 internal constant MIN_BALANCE = BONE / 10**12; - // Initial pool tokens - uint256 internal constant INIT_POOL_SUPPLY = BONE * 100; - - uint256 internal constant MIN_BPOW_BASE = 1 wei; - uint256 internal constant MAX_BPOW_BASE = (2 * BONE) - 1 wei; - uint256 internal constant BPOW_PRECISION = BONE / 10**10; - - // Maximum ratio of input tokens to balance for swaps. - uint256 internal constant MAX_IN_RATIO = BONE / 2; - // Maximum ratio of output tokens to balance for swaps. - uint256 internal constant MAX_OUT_RATIO = (BONE / 3) + 1 wei; -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BMath.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BMath.sol deleted file mode 100644 index 47593291a2..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BMath.sol +++ /dev/null @@ -1,252 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.6.0; - -import "./BNum.sol"; - - -/************************************************************************************************ -Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BMath.sol - -This source code has been modified from the original, which was copied from the github repository -at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. - -Subject to the GPL-3.0 license -*************************************************************************************************/ - - -contract BMath is BConst, BNum { - /********************************************************************************************** - // calcSpotPrice // - // sP = spotPrice // - // bI = tokenBalanceIn ( bI / wI ) 1 // - // bO = tokenBalanceOut sP = ----------- * ---------- // - // wI = tokenWeightIn ( bO / wO ) ( 1 - sF ) // - // wO = tokenWeightOut // - // sF = swapFee // - **********************************************************************************************/ - function calcSpotPrice( - uint256 tokenBalanceIn, - uint256 tokenWeightIn, - uint256 tokenBalanceOut, - uint256 tokenWeightOut, - uint256 swapFee - ) internal pure returns (uint256 spotPrice) { - uint256 numer = bdiv(tokenBalanceIn, tokenWeightIn); - uint256 denom = bdiv(tokenBalanceOut, tokenWeightOut); - uint256 ratio = bdiv(numer, denom); - uint256 scale = bdiv(BONE, bsub(BONE, swapFee)); - return (spotPrice = bmul(ratio, scale)); - } - - /********************************************************************************************** - // calcOutGivenIn // - // aO = tokenAmountOut // - // bO = tokenBalanceOut // - // bI = tokenBalanceIn / / bI \ (wI / wO) \ // - // aI = tokenAmountIn aO = bO * | 1 - | -------------------------- | ^ | // - // wI = tokenWeightIn \ \ ( bI + ( aI * ( 1 - sF )) / / // - // wO = tokenWeightOut // - // sF = swapFee // - **********************************************************************************************/ - function calcOutGivenIn( - uint256 tokenBalanceIn, - uint256 tokenWeightIn, - uint256 tokenBalanceOut, - uint256 tokenWeightOut, - uint256 tokenAmountIn, - uint256 swapFee - ) internal pure returns (uint256 tokenAmountOut) { - uint256 weightRatio = bdiv(tokenWeightIn, tokenWeightOut); - uint256 adjustedIn = bsub(BONE, swapFee); - adjustedIn = bmul(tokenAmountIn, adjustedIn); - uint256 y = bdiv(tokenBalanceIn, badd(tokenBalanceIn, adjustedIn)); - uint256 foo = bpow(y, weightRatio); - uint256 bar = bsub(BONE, foo); - tokenAmountOut = bmul(tokenBalanceOut, bar); - return tokenAmountOut; - } - - /********************************************************************************************** - // calcInGivenOut // - // aI = tokenAmountIn // - // bO = tokenBalanceOut / / bO \ (wO / wI) \ // - // bI = tokenBalanceIn bI * | | ------------ | ^ - 1 | // - // aO = tokenAmountOut aI = \ \ ( bO - aO ) / / // - // wI = tokenWeightIn -------------------------------------------- // - // wO = tokenWeightOut ( 1 - sF ) // - // sF = swapFee // - **********************************************************************************************/ - function calcInGivenOut( - uint256 tokenBalanceIn, - uint256 tokenWeightIn, - uint256 tokenBalanceOut, - uint256 tokenWeightOut, - uint256 tokenAmountOut, - uint256 swapFee - ) internal pure returns (uint256 tokenAmountIn) { - uint256 weightRatio = bdiv(tokenWeightOut, tokenWeightIn); - uint256 diff = bsub(tokenBalanceOut, tokenAmountOut); - uint256 y = bdiv(tokenBalanceOut, diff); - uint256 foo = bpow(y, weightRatio); - foo = bsub(foo, BONE); - tokenAmountIn = bsub(BONE, swapFee); - tokenAmountIn = bdiv(bmul(tokenBalanceIn, foo), tokenAmountIn); - return tokenAmountIn; - } - - /********************************************************************************************** - // calcPoolOutGivenSingleIn // - // pAo = poolAmountOut / \ // - // tAi = tokenAmountIn /// / // wI \ \\ \ wI \ // - // wI = tokenWeightIn //| tAi *| 1 - || 1 - -- | * sF || + tBi \ -- \ // - // tW = totalWeight pAo=|| \ \ \\ tW / // | ^ tW | * pS - pS // - // tBi = tokenBalanceIn \\ ------------------------------------- / / // - // pS = poolSupply \\ tBi / / // - // sF = swapFee \ / // - **********************************************************************************************/ - function calcPoolOutGivenSingleIn( - uint256 tokenBalanceIn, - uint256 tokenWeightIn, - uint256 poolSupply, - uint256 totalWeight, - uint256 tokenAmountIn, - uint256 swapFee - ) internal pure returns (uint256 poolAmountOut) { - // Charge the trading fee for the proportion of tokenAi - /// which is implicitly traded to the other pool tokens. - // That proportion is (1- weightTokenIn) - // tokenAiAfterFee = tAi * (1 - (1-weightTi) * poolFee); - uint256 normalizedWeight = bdiv(tokenWeightIn, totalWeight); - uint256 zaz = bmul(bsub(BONE, normalizedWeight), swapFee); - uint256 tokenAmountInAfterFee = bmul(tokenAmountIn, bsub(BONE, zaz)); - - uint256 newTokenBalanceIn = badd(tokenBalanceIn, tokenAmountInAfterFee); - uint256 tokenInRatio = bdiv(newTokenBalanceIn, tokenBalanceIn); - - // uint newPoolSupply = (ratioTi ^ weightTi) * poolSupply; - uint256 poolRatio = bpow(tokenInRatio, normalizedWeight); - uint256 newPoolSupply = bmul(poolRatio, poolSupply); - poolAmountOut = bsub(newPoolSupply, poolSupply); - return poolAmountOut; - } - - /********************************************************************************************** - // calcSingleInGivenPoolOut // - // tAi = tokenAmountIn //(pS + pAo)\ / 1 \\ // - // pS = poolSupply || --------- | ^ | --------- || * bI - bI // - // pAo = poolAmountOut \\ pS / \(wI / tW)// // - // bI = balanceIn tAi = -------------------------------------------- // - // wI = weightIn / wI \ // - // tW = totalWeight | 1 - ---- | * sF // - // sF = swapFee \ tW / // - **********************************************************************************************/ - function calcSingleInGivenPoolOut( - uint256 tokenBalanceIn, - uint256 tokenWeightIn, - uint256 poolSupply, - uint256 totalWeight, - uint256 poolAmountOut, - uint256 swapFee - ) internal pure returns (uint256 tokenAmountIn) { - uint256 normalizedWeight = bdiv(tokenWeightIn, totalWeight); - uint256 newPoolSupply = badd(poolSupply, poolAmountOut); - uint256 poolRatio = bdiv(newPoolSupply, poolSupply); - - //uint newBalTi = poolRatio^(1/weightTi) * balTi; - uint256 boo = bdiv(BONE, normalizedWeight); - uint256 tokenInRatio = bpow(poolRatio, boo); - uint256 newTokenBalanceIn = bmul(tokenInRatio, tokenBalanceIn); - uint256 tokenAmountInAfterFee = bsub(newTokenBalanceIn, tokenBalanceIn); - // Do reverse order of fees charged in joinswap_ExternAmountIn, this way - // ``` pAo == joinswap_ExternAmountIn(Ti, joinswap_PoolAmountOut(pAo, Ti)) ``` - //uint tAi = tAiAfterFee / (1 - (1-weightTi) * swapFee) ; - uint256 zar = bmul(bsub(BONE, normalizedWeight), swapFee); - tokenAmountIn = bdiv(tokenAmountInAfterFee, bsub(BONE, zar)); - return tokenAmountIn; - } - - /********************************************************************************************** - // calcSingleOutGivenPoolIn // - // tAo = tokenAmountOut / / \\ // - // bO = tokenBalanceOut / // pS - (pAi * (1 - eF)) \ / 1 \ \\ // - // pAi = poolAmountIn | bO - || ----------------------- | ^ | --------- | * b0 || // - // ps = poolSupply \ \\ pS / \(wO / tW)/ // // - // wI = tokenWeightIn tAo = \ \ // // - // tW = totalWeight / / wO \ \ // - // sF = swapFee * | 1 - | 1 - ---- | * sF | // - // eF = exitFee \ \ tW / / // - **********************************************************************************************/ - function calcSingleOutGivenPoolIn( - uint256 tokenBalanceOut, - uint256 tokenWeightOut, - uint256 poolSupply, - uint256 totalWeight, - uint256 poolAmountIn, - uint256 swapFee - ) internal pure returns (uint256 tokenAmountOut) { - uint256 normalizedWeight = bdiv(tokenWeightOut, totalWeight); - // charge exit fee on the pool token side - // pAiAfterExitFee = pAi*(1-exitFee) - uint256 poolAmountInAfterExitFee = bmul(poolAmountIn, bsub(BONE, EXIT_FEE)); - uint256 newPoolSupply = bsub(poolSupply, poolAmountInAfterExitFee); - uint256 poolRatio = bdiv(newPoolSupply, poolSupply); - - // newBalTo = poolRatio^(1/weightTo) * balTo; - uint256 tokenOutRatio = bpow(poolRatio, bdiv(BONE, normalizedWeight)); - uint256 newTokenBalanceOut = bmul(tokenOutRatio, tokenBalanceOut); - - uint256 tokenAmountOutBeforeSwapFee = bsub( - tokenBalanceOut, - newTokenBalanceOut - ); - - // charge swap fee on the output token side - //uint tAo = tAoBeforeSwapFee * (1 - (1-weightTo) * swapFee) - uint256 zaz = bmul(bsub(BONE, normalizedWeight), swapFee); - tokenAmountOut = bmul(tokenAmountOutBeforeSwapFee, bsub(BONE, zaz)); - return tokenAmountOut; - } - - /********************************************************************************************** - // calcPoolInGivenSingleOut // - // pAi = poolAmountIn // / tAo \\ / wO \ \ // - // bO = tokenBalanceOut // | bO - -------------------------- |\ | ---- | \ // - // tAo = tokenAmountOut pS - || \ 1 - ((1 - (tO / tW)) * sF)/ | ^ \ tW / * pS | // - // ps = poolSupply \\ -----------------------------------/ / // - // wO = tokenWeightOut pAi = \\ bO / / // - // tW = totalWeight ------------------------------------------------------------- // - // sF = swapFee ( 1 - eF ) // - // eF = exitFee // - **********************************************************************************************/ - function calcPoolInGivenSingleOut( - uint256 tokenBalanceOut, - uint256 tokenWeightOut, - uint256 poolSupply, - uint256 totalWeight, - uint256 tokenAmountOut, - uint256 swapFee - ) internal pure returns (uint256 poolAmountIn) { - // charge swap fee on the output token side - uint256 normalizedWeight = bdiv(tokenWeightOut, totalWeight); - //uint tAoBeforeSwapFee = tAo / (1 - (1-weightTo) * swapFee) ; - uint256 zoo = bsub(BONE, normalizedWeight); - uint256 zar = bmul(zoo, swapFee); - uint256 tokenAmountOutBeforeSwapFee = bdiv(tokenAmountOut, bsub(BONE, zar)); - - uint256 newTokenBalanceOut = bsub( - tokenBalanceOut, - tokenAmountOutBeforeSwapFee - ); - uint256 tokenOutRatio = bdiv(newTokenBalanceOut, tokenBalanceOut); - - //uint newPoolSupply = (ratioTo ^ weightTo) * poolSupply; - uint256 poolRatio = bpow(tokenOutRatio, normalizedWeight); - uint256 newPoolSupply = bmul(poolRatio, poolSupply); - uint256 poolAmountInAfterExitFee = bsub(poolSupply, newPoolSupply); - - // charge exit fee on the pool token side - // pAi = pAiAfterExitFee/(1-exitFee) - poolAmountIn = bdiv(poolAmountInAfterExitFee, bsub(BONE, EXIT_FEE)); - return poolAmountIn; - } -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BNum.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BNum.sol deleted file mode 100644 index 8dfe7689d0..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BNum.sol +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.6.0; - -import "./BConst.sol"; - - -/************************************************************************************************ -Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BNum.sol - -This source code has been modified from the original, which was copied from the github repository -at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. - -Subject to the GPL-3.0 license -*************************************************************************************************/ - - -contract BNum is BConst { - function btoi(uint256 a) internal pure returns (uint256) { - return a / BONE; - } - - function bfloor(uint256 a) internal pure returns (uint256) { - return btoi(a) * BONE; - } - - function badd(uint256 a, uint256 b) internal pure returns (uint256) { - uint256 c = a + b; - require(c >= a, "ERR_ADD_OVERFLOW"); - return c; - } - - function bsub(uint256 a, uint256 b) internal pure returns (uint256) { - (uint256 c, bool flag) = bsubSign(a, b); - require(!flag, "ERR_SUB_UNDERFLOW"); - return c; - } - - function bsubSign(uint256 a, uint256 b) - internal - pure - returns (uint256, bool) - { - if (a >= b) { - return (a - b, false); - } else { - return (b - a, true); - } - } - - function bmul(uint256 a, uint256 b) internal pure returns (uint256) { - uint256 c0 = a * b; - require(a == 0 || c0 / a == b, "ERR_MUL_OVERFLOW"); - uint256 c1 = c0 + (BONE / 2); - require(c1 >= c0, "ERR_MUL_OVERFLOW"); - uint256 c2 = c1 / BONE; - return c2; - } - - function bdiv(uint256 a, uint256 b) internal pure returns (uint256) { - require(b != 0, "ERR_DIV_ZERO"); - uint256 c0 = a * BONE; - require(a == 0 || c0 / a == BONE, "ERR_DIV_INTERNAL"); // bmul overflow - uint256 c1 = c0 + (b / 2); - require(c1 >= c0, "ERR_DIV_INTERNAL"); // badd require - uint256 c2 = c1 / b; - return c2; - } - - // DSMath.wpow - function bpowi(uint256 a, uint256 n) internal pure returns (uint256) { - uint256 z = n % 2 != 0 ? a : BONE; - - for (n /= 2; n != 0; n /= 2) { - a = bmul(a, a); - - if (n % 2 != 0) { - z = bmul(z, a); - } - } - return z; - } - - // Compute b^(e.w) by splitting it into (b^e)*(b^0.w). - // Use `bpowi` for `b^e` and `bpowK` for k iterations - // of approximation of b^0.w - function bpow(uint256 base, uint256 exp) internal pure returns (uint256) { - require(base >= MIN_BPOW_BASE, "ERR_BPOW_BASE_TOO_LOW"); - require(base <= MAX_BPOW_BASE, "ERR_BPOW_BASE_TOO_HIGH"); - - uint256 whole = bfloor(exp); - uint256 remain = bsub(exp, whole); - - uint256 wholePow = bpowi(base, btoi(whole)); - - if (remain == 0) { - return wholePow; - } - - uint256 partialResult = bpowApprox(base, remain, BPOW_PRECISION); - return bmul(wholePow, partialResult); - } - - function bpowApprox( - uint256 base, - uint256 exp, - uint256 precision - ) internal pure returns (uint256) { - // term 0: - uint256 a = exp; - (uint256 x, bool xneg) = bsubSign(base, BONE); - uint256 term = BONE; - uint256 sum = term; - bool negative = false; - - // term(k) = numer / denom - // = (product(a - i - 1, i=1-->k) * x^k) / (k!) - // each iteration, multiply previous term by (a-(k-1)) * x / k - // continue until term is less than precision - for (uint256 i = 1; term >= precision; i++) { - uint256 bigK = i * BONE; - (uint256 c, bool cneg) = bsubSign(a, bsub(bigK, BONE)); - term = bmul(term, bmul(c, x)); - term = bdiv(term, bigK); - if (term == 0) break; - - if (xneg) negative = !negative; - if (cneg) negative = !negative; - if (negative) { - sum = bsub(sum, term); - } else { - sum = badd(sum, term); - } - } - - return sum; - } -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BToken.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BToken.sol deleted file mode 100644 index 1053a7f002..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BToken.sol +++ /dev/null @@ -1,192 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.6.0; - -import "./BNum.sol"; - - -/************************************************************************************************ -Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BToken.sol - -This source code has been modified from the original, which was copied from the github repository -at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. - -Subject to the GPL-3.0 license -*************************************************************************************************/ - - -// Highly opinionated token implementation -interface IERC20 { - event Approval(address indexed src, address indexed dst, uint256 amt); - event Transfer(address indexed src, address indexed dst, uint256 amt); - - function name() external view returns (string memory); - - function symbol() external view returns (string memory); - - function decimals() external view returns (uint8); - - function totalSupply() external view returns (uint256); - - function balanceOf(address whom) external view returns (uint256); - - function allowance(address src, address dst) external view returns (uint256); - - function approve(address dst, uint256 amt) external returns (bool); - - function transfer(address dst, uint256 amt) external returns (bool); - - function transferFrom( - address src, - address dst, - uint256 amt - ) external returns (bool); -} - - -contract BTokenBase is BNum { - mapping(address => uint256) internal _balance; - mapping(address => mapping(address => uint256)) internal _allowance; - uint256 internal _totalSupply; - - event Approval(address indexed src, address indexed dst, uint256 amt); - event Transfer(address indexed src, address indexed dst, uint256 amt); - - function _mint(uint256 amt) internal { - _balance[address(this)] = badd(_balance[address(this)], amt); - _totalSupply = badd(_totalSupply, amt); - emit Transfer(address(0), address(this), amt); - } - - function _burn(uint256 amt) internal { - require(_balance[address(this)] >= amt, "ERR_INSUFFICIENT_BAL"); - _balance[address(this)] = bsub(_balance[address(this)], amt); - _totalSupply = bsub(_totalSupply, amt); - emit Transfer(address(this), address(0), amt); - } - - function _move( - address src, - address dst, - uint256 amt - ) internal { - require(_balance[src] >= amt, "ERR_INSUFFICIENT_BAL"); - _balance[src] = bsub(_balance[src], amt); - _balance[dst] = badd(_balance[dst], amt); - emit Transfer(src, dst, amt); - } - - function _push(address to, uint256 amt) internal { - _move(address(this), to, amt); - } - - function _pull(address from, uint256 amt) internal { - _move(from, address(this), amt); - } -} - - -contract BToken is BTokenBase, IERC20 { - uint8 private constant DECIMALS = 18; - string private _name; - string private _symbol; - - function _initializeToken(string memory name, string memory symbol) internal { - require( - bytes(_name).length == 0 && - bytes(name).length != 0 && - bytes(symbol).length != 0, - "ERR_BTOKEN_INITIALIZED" - ); - _name = name; - _symbol = symbol; - } - - function name() - external - override - view - returns (string memory) - { - return _name; - } - - function symbol() - external - override - view - returns (string memory) - { - return _symbol; - } - - function decimals() - external - override - view - returns (uint8) - { - return DECIMALS; - } - - function allowance(address src, address dst) - external - override - view - returns (uint256) - { - return _allowance[src][dst]; - } - - function balanceOf(address whom) external override view returns (uint256) { - return _balance[whom]; - } - - function totalSupply() public override view returns (uint256) { - return _totalSupply; - } - - function approve(address dst, uint256 amt) external override returns (bool) { - _allowance[msg.sender][dst] = amt; - emit Approval(msg.sender, dst, amt); - return true; - } - - function increaseApproval(address dst, uint256 amt) external returns (bool) { - _allowance[msg.sender][dst] = badd(_allowance[msg.sender][dst], amt); - emit Approval(msg.sender, dst, _allowance[msg.sender][dst]); - return true; - } - - function decreaseApproval(address dst, uint256 amt) external returns (bool) { - uint256 oldValue = _allowance[msg.sender][dst]; - if (amt > oldValue) { - _allowance[msg.sender][dst] = 0; - } else { - _allowance[msg.sender][dst] = bsub(oldValue, amt); - } - emit Approval(msg.sender, dst, _allowance[msg.sender][dst]); - return true; - } - - function transfer(address dst, uint256 amt) external override returns (bool) { - _move(msg.sender, dst, amt); - return true; - } - - function transferFrom( - address src, - address dst, - uint256 amt - ) external override returns (bool) { - require( - msg.sender == src || amt <= _allowance[src][msg.sender], - "ERR_BTOKEN_BAD_CALLER" - ); - _move(src, dst, amt); - if (msg.sender != src && _allowance[src][msg.sender] != uint256(-1)) { - _allowance[src][msg.sender] = bsub(_allowance[src][msg.sender], amt); - emit Approval(msg.sender, dst, _allowance[src][msg.sender]); - } - return true; - } -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BeefyVault.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BeefyVault.sol deleted file mode 100644 index d855711d9c..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/BeefyVault.sol +++ /dev/null @@ -1,941 +0,0 @@ -/** - *Submitted for verification at BscScan.com on 2020-10-27 -*/ - -// SPDX-License-Identifier: MIT -// File: @openzeppelin/contracts/GSN/Context.sol - - -pragma solidity ^0.6.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/token/ERC20/IERC20.sol - - -pragma solidity ^0.6.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; - -/** - * @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; - } -} - -// File: @openzeppelin/contracts/utils/Address.sol - - -pragma solidity ^0.6.2; - -/** - * @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); - } - } - } -} - -// File: @openzeppelin/contracts/token/ERC20/ERC20.sol - - -pragma solidity ^0.6.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 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 { - using SafeMath for uint256; - using Address for address; - - mapping (address => uint256) private _balances; - - mapping (address => mapping (address => uint256)) private _allowances; - - uint256 private _totalSupply; - - string private _name; - string private _symbol; - uint8 private _decimals; - - /** - * @dev Sets the values for {name} and {symbol}, initializes {decimals} with - * a default value of 18. - * - * To select a different value for {decimals}, use {_setupDecimals}. - * - * All three of these values are immutable: they can only be set once during - * construction. - */ - constructor (string memory name, string memory symbol) public { - _name = name; - _symbol = symbol; - _decimals = 18; - } - - /** - * @dev Returns the name of the token. - */ - function name() public view returns (string memory) { - return _name; - } - - /** - * @dev Returns the symbol of the token, usually a shorter version of the - * name. - */ - function symbol() public view 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 {_setupDecimals} is - * called. - * - * 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 returns (uint8) { - return _decimals; - } - - /** - * @dev See {IERC20-totalSupply}. - */ - function totalSupply() public view override returns (uint256) { - return _totalSupply; - } - - /** - * @dev See {IERC20-balanceOf}. - */ - function balanceOf(address account) public view 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); - _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); - 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].add(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) { - _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); - 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); - - _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); - _balances[recipient] = _balances[recipient].add(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 = _totalSupply.add(amount); - _balances[account] = _balances[account].add(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); - - _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); - _totalSupply = _totalSupply.sub(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 Sets {decimals} to a value other than the default one of 18. - * - * WARNING: This function should only be called from the constructor. Most - * applications that interact with token contracts will not expect - * {decimals} to ever change, and may work incorrectly if it does. - */ - function _setupDecimals(uint8 decimals_) internal { - _decimals = decimals_; - } - - /** - * @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: @openzeppelin/contracts/token/ERC20/SafeERC20.sol - - -pragma solidity ^0.6.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/BIFI/interfaces/beefy/IStrategy.sol - - -pragma solidity ^0.6.0; - -interface IStrategy { - function want() external view returns (address); - function deposit() external; - function withdraw(uint256) external; - function balanceOf() external view returns (uint256); -} - -// File: contracts/BIFI/vaults/BeefyVault.sol - - -pragma solidity ^0.6.0; - - - - - - -/** - * @dev Implementation of a vault to deposit funds for yield optimizing. - * This is the contract that receives funds and that users interface with. - * The yield optimizing strategy itself is implemented in a separate 'Strategy.sol' contract. - */ -contract BeefyVault is ERC20 { - using SafeERC20 for IERC20; - using Address for address; - using SafeMath for uint256; - - IERC20 public token; - address public strategy; - - /** - * @dev Sets the value of {token} to the token that the vault will - * hold as underlying value. It initializes the vault's own 'moo' token. - * This token is minted when someone does a deposit. It is burned in order - * to withdraw the corresponding portion of the underlying assets. - * @param _token the token to maximize. - * @param _strategy the address of the strategy. - * @param _name the name of the vault token. - * @param _symbol the symbol of the vault token. - */ - constructor (address _token, address _strategy, string memory _name, string memory _symbol) public ERC20( - string(abi.encodePacked(_name)), - string(abi.encodePacked(_symbol)) - ) { - token = IERC20(_token); - strategy = _strategy; - } - - /** - * @dev It calculates the total underlying value of {token} held by the system. - * It takes into account the vault contract balance, the strategy contract balance - * and the balance deployed in other contracts as part of the strategy. - */ - function balance() public view returns (uint) { - return token.balanceOf(address(this)) - .add(IStrategy(strategy).balanceOf()); - } - - /** - * @dev Custom logic in here for how much the vault allows to be borrowed. - * We return 100% of tokens for now. Under certain conditions we might - * want to keep some of the system funds at hand in the vault, instead - * of putting them to work. - */ - function available() public view returns (uint) { - return token.balanceOf(address(this)); - } - - /** - * @dev Function for various UIs to display the current value of one of our yield tokens. - * Returns an uint with 18 decimals of how much underlying asset one vault share represents. - */ - function getPricePerFullShare() public view returns (uint) { - return balance().mul(1e18).div(totalSupply()); - } - - /** - * @dev A helper function to call deposit() with all the sender's funds. - */ - function depositAll() external { - deposit(token.balanceOf(msg.sender)); - } - - /** - * @dev The entrypoint of funds into the system. People deposit with this function - * into the vault. The vault is then in charge of sending funds into the strategy. - */ - function deposit(uint _amount) public { - uint _pool = balance(); - uint _before = token.balanceOf(address(this)); - token.safeTransferFrom(msg.sender, address(this), _amount); - uint _after = token.balanceOf(address(this)); - _amount = _after.sub(_before); // Additional check for deflationary tokens - uint shares = 0; - if (totalSupply() == 0) { - shares = _amount; - } else { - shares = (_amount.mul(totalSupply())).div(_pool); - } - _mint(msg.sender, shares); - - earn(); - } - - /** - * @dev Function to send funds into the strategy and put them to work. It's primarily called - * by the vault's deposit() function. - */ - function earn() public { - uint _bal = available(); - token.safeTransfer(strategy, _bal); - IStrategy(strategy).deposit(); - } - - /** - * @dev A helper function to call withdraw() with all the sender's funds. - */ - function withdrawAll() external { - withdraw(balanceOf(msg.sender)); - } - - /** - * @dev Function to exit the system. The vault will withdraw the required tokens - * from the strategy and pay up the token holder. A proportional number of IOU - * tokens are burned in the process. - */ - function withdraw(uint _shares) public { - uint _withdraw = (balance().mul(_shares)).div(totalSupply()); - _burn(msg.sender, _shares); - - uint _before = token.balanceOf(address(this)); - IStrategy(strategy).withdraw(_withdraw); - uint _after = token.balanceOf(address(this)); - uint _diff = _after.sub(_before); - - token.safeTransfer(msg.sender, _diff); - } -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/ICompLikeToken.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/ICompLikeToken.sol deleted file mode 100644 index f98b034d68..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/ICompLikeToken.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.6.0; - - -interface ICompLikeToken { - function delegate(address delegatee) external; -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/IIndexPool.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/IIndexPool.sol deleted file mode 100644 index 3a499c389b..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/IIndexPool.sol +++ /dev/null @@ -1,202 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.6.0; -pragma experimental ABIEncoderV2; - - -interface IIndexPool { - /** - * @dev Token record data structure - * @param bound is token bound to pool - * @param ready has token been initialized - * @param lastDenormUpdate timestamp of last denorm change - * @param denorm denormalized weight - * @param desiredDenorm desired denormalized weight (used for incremental changes) - * @param index index of address in tokens array - * @param balance token balance - */ - struct Record { - bool bound; - bool ready; - uint40 lastDenormUpdate; - uint96 denorm; - uint96 desiredDenorm; - uint8 index; - uint256 balance; - } - -/* ========== EVENTS ========== */ - - /** @dev Emitted when tokens are swapped. */ - event LOG_SWAP( - address indexed caller, - address indexed tokenIn, - address indexed tokenOut, - uint256 tokenAmountIn, - uint256 tokenAmountOut - ); - - /** @dev Emitted when underlying tokens are deposited for pool tokens. */ - event LOG_JOIN( - address indexed caller, - address indexed tokenIn, - uint256 tokenAmountIn - ); - - /** @dev Emitted when pool tokens are burned for underlying. */ - event LOG_EXIT( - address indexed caller, - address indexed tokenOut, - uint256 tokenAmountOut - ); - - /** @dev Emitted when a token's weight updates. */ - event LOG_DENORM_UPDATED(address indexed token, uint256 newDenorm); - - /** @dev Emitted when a token's desired weight is set. */ - event LOG_DESIRED_DENORM_SET(address indexed token, uint256 desiredDenorm); - - /** @dev Emitted when a token is unbound from the pool. */ - event LOG_TOKEN_REMOVED(address token); - - /** @dev Emitted when a token is unbound from the pool. */ - event LOG_TOKEN_ADDED( - address indexed token, - uint256 desiredDenorm, - uint256 minimumBalance - ); - - /** @dev Emitted when a token's minimum balance is updated. */ - event LOG_MINIMUM_BALANCE_UPDATED(address token, uint256 minimumBalance); - - /** @dev Emitted when a token reaches its minimum balance. */ - event LOG_TOKEN_READY(address indexed token); - - /** @dev Emitted when public trades are enabled. */ - event LOG_PUBLIC_SWAP_ENABLED(); - - /** @dev Emitted when the swap fee is updated. */ - event LOG_SWAP_FEE_UPDATED(uint256 swapFee); - - /** @dev Emitted when exit fee recipient is updated. */ - event LOG_EXIT_FEE_RECIPIENT_UPDATED(address exitFeeRecipient); - - /** @dev Emitted when controller is updated. */ - event LOG_CONTROLLER_UPDATED(address exitFeeRecipient); - - function configure( - address controller, - string calldata name, - string calldata symbol, - address exitFeeRecipient - ) external; - - function initialize( - address[] calldata tokens, - uint256[] calldata balances, - uint96[] calldata denorms, - address tokenProvider, - address unbindHandler - ) external; - - function setSwapFee(uint256 swapFee) external; - - function delegateCompLikeToken(address token, address delegatee) external; - - function setExitFeeRecipient(address exitFeeRecipient) external; - - function setController(address controller) external; - - function reweighTokens( - address[] calldata tokens, - uint96[] calldata desiredDenorms - ) external; - - function reindexTokens( - address[] calldata tokens, - uint96[] calldata desiredDenorms, - uint256[] calldata minimumBalances - ) external; - - function setMinimumBalance(address token, uint256 minimumBalance) external; - - function joinPool(uint256 poolAmountOut, uint256[] calldata maxAmountsIn) external; - - function joinswapExternAmountIn( - address tokenIn, - uint256 tokenAmountIn, - uint256 minPoolAmountOut - ) external returns (uint256/* poolAmountOut */); - - 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 gulp(address token) external; - - function swapExactAmountIn( - address tokenIn, - uint256 tokenAmountIn, - address tokenOut, - uint256 minAmountOut, - uint256 maxPrice - ) external returns (uint256/* tokenAmountOut */, uint256/* spotPriceAfter */); - - function swapExactAmountOut( - address tokenIn, - uint256 maxAmountIn, - address tokenOut, - uint256 tokenAmountOut, - uint256 maxPrice - ) external returns (uint256 /* tokenAmountIn */, uint256 /* spotPriceAfter */); - - function isPublicSwap() external view returns (bool); - - function getSwapFee() external view returns (uint256/* swapFee */); - - function getExitFee() external view returns (uint256/* exitFee */); - - function getController() external view returns (address); - - function getExitFeeRecipient() external view returns (address); - - function isBound(address t) external view returns (bool); - - function getNumTokens() external view returns (uint256); - - function getCurrentTokens() external view returns (address[] memory tokens); - - function getCurrentDesiredTokens() external view returns (address[] memory tokens); - - function getDenormalizedWeight(address token) external view returns (uint256/* denorm */); - - function getTokenRecord(address token) external view returns (Record memory record); - - function extrapolatePoolValueFromToken() external view returns (address/* token */, uint256/* extrapolatedValue */); - - function getTotalDenormalizedWeight() external view returns (uint256); - - function getBalance(address token) external view returns (uint256); - - function getMinimumBalance(address token) external view returns (uint256); - - function getUsedBalance(address token) external view returns (uint256); - - function getSpotPrice(address tokenIn, address tokenOut) external view returns (uint256); -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/IndexPool.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/IndexPool.sol deleted file mode 100644 index 647c59bdb3..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/IndexPool.sol +++ /dev/null @@ -1,1368 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.6.0; -pragma experimental ABIEncoderV2; - -/* ========== Internal Inheritance ========== */ -import "./BToken.sol"; -import "./BMath.sol"; - -/* ========== Internal Interfaces ========== */ -import "./IIndexPool.sol"; -import "./ICompLikeToken.sol"; - - -/************************************************************************************************ -Originally from https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol - -This source code has been modified from the original, which was copied from the github repository -at commit hash f4ed5d65362a8d6cec21662fb6eae233b0babc1f. - -Subject to the GPL-3.0 license -*************************************************************************************************/ - - -contract IndexPool is BToken, BMath, IIndexPool { -/* ========== Modifiers ========== */ - - modifier _lock_ { - require(!_mutex, "ERR_REENTRY"); - _mutex = true; - _; - _mutex = false; - } - - modifier _viewlock_() { - require(!_mutex, "ERR_REENTRY"); - _; - } - - modifier _control_ { - require(msg.sender == _controller, "ERR_NOT_CONTROLLER"); - _; - } - - modifier _public_ { - require(_publicSwap, "ERR_NOT_PUBLIC"); - _; - } - -/* ========== Storage ========== */ - - bool internal _mutex; - - // Account with CONTROL role. Able to modify the swap fee, - // adjust token weights, bind and unbind tokens and lock - // public swaps & joins. - address internal _controller; - - // Contract that handles unbound tokens. - TokenUnbindHandler internal _unbindHandler; - - // True if PUBLIC can call SWAP & JOIN functions - bool internal _publicSwap; - - // `setSwapFee` requires CONTROL - uint256 internal _swapFee; - - // Array of underlying tokens in the pool. - address[] internal _tokens; - - // Internal records of the pool's underlying tokens - mapping(address => Record) internal _records; - - // Total denormalized weight of the pool. - uint256 internal _totalWeight; - - // Minimum balances for tokens which have been added without the - // requisite initial balance. - mapping(address => uint256) internal _minimumBalances; - - // Recipient for exit fees - address internal _exitFeeRecipient; - -/* ========== Controls ========== */ - - /** - * @dev Sets the controller address and the token name & symbol. - * - * Note: This saves on storage costs for multi-step pool deployment. - * - * @param controller Controller of the pool - * @param name Name of the pool token - * @param symbol Symbol of the pool token - * @param exitFeeRecipient Address that receives exit fees - */ - function configure( - address controller, - string calldata name, - string calldata symbol, - address exitFeeRecipient - ) external override { - require(_controller == address(0), "ERR_CONFIGURED"); - require(controller != address(0) && exitFeeRecipient != address(0), "ERR_NULL_ADDRESS"); - _controller = controller; - // default fee is 2% - _swapFee = BONE / 50; - _exitFeeRecipient = exitFeeRecipient; - _initializeToken(name, symbol); - } - - /** - * @dev Sets up the initial assets for the pool. - * - * Note: `tokenProvider` must have approved the pool to transfer the - * corresponding `balances` of `tokens`. - * - * @param tokens Underlying tokens to initialize the pool with - * @param balances Initial balances to transfer - * @param denorms Initial denormalized weights for the tokens - * @param tokenProvider Address to transfer the balances from - */ - function initialize( - address[] calldata tokens, - uint256[] calldata balances, - uint96[] calldata denorms, - address tokenProvider, - address unbindHandler - ) - external - override - _control_ - { - require(_tokens.length == 0, "ERR_INITIALIZED"); - uint256 len = tokens.length; - require(len >= MIN_BOUND_TOKENS, "ERR_MIN_TOKENS"); - require(len <= MAX_BOUND_TOKENS, "ERR_MAX_TOKENS"); - require(balances.length == len && denorms.length == len, "ERR_ARR_LEN"); - uint256 totalWeight = 0; - for (uint256 i = 0; i < len; i++) { - address token = tokens[i]; - uint96 denorm = denorms[i]; - uint256 balance = balances[i]; - require(denorm >= MIN_WEIGHT, "ERR_MIN_WEIGHT"); - require(denorm <= MAX_WEIGHT, "ERR_MAX_WEIGHT"); - require(balance >= MIN_BALANCE, "ERR_MIN_BALANCE"); - _records[token] = Record({ - bound: true, - ready: true, - lastDenormUpdate: uint40(now), - denorm: denorm, - desiredDenorm: denorm, - index: uint8(i), - balance: balance - }); - _tokens.push(token); - totalWeight = badd(totalWeight, denorm); - _pullUnderlying(token, tokenProvider, balance); - } - require(totalWeight <= MAX_TOTAL_WEIGHT, "ERR_MAX_TOTAL_WEIGHT"); - _totalWeight = totalWeight; - _publicSwap = true; - emit LOG_PUBLIC_SWAP_ENABLED(); - _mintPoolShare(INIT_POOL_SUPPLY); - _pushPoolShare(tokenProvider, INIT_POOL_SUPPLY); - _unbindHandler = TokenUnbindHandler(unbindHandler); - } - - /** - * @dev Set the swap fee. - * Note: Swap fee must be between 0.0001% and 10% - */ - function setSwapFee(uint256 swapFee) external override _control_ { - require(swapFee >= MIN_FEE && swapFee <= MAX_FEE, "ERR_INVALID_FEE"); - _swapFee = swapFee; - emit LOG_SWAP_FEE_UPDATED(swapFee); - } - - /** - * @dev Delegate a comp-like governance token to an address - * specified by the controller. - */ - function delegateCompLikeToken(address token,address delegatee) - external - override - _control_ - { - ICompLikeToken(token).delegate(delegatee); - } - - /** - * @dev Set the exit fee recipient address. - */ - function setExitFeeRecipient(address exitFeeRecipient) external override _control_ { - require(exitFeeRecipient != address(0), "ERR_NULL_ADDRESS"); - _exitFeeRecipient = exitFeeRecipient; - emit LOG_EXIT_FEE_RECIPIENT_UPDATED(exitFeeRecipient); - } - - /** - * @dev Set the controller address - */ - function setController(address controller) external override _control_ { - require(controller != address(0), "ERR_NULL_ADDRESS"); - _controller = controller; - emit LOG_CONTROLLER_UPDATED(controller); - } - -/* ========== Token Management Actions ========== */ - - /** - * @dev Sets the desired weights for the pool tokens, which - * will be adjusted over time as they are swapped. - * - * Note: This does not check for duplicate tokens or that the total - * of the desired weights is equal to the target total weight (25). - * Those assumptions should be met in the controller. Further, the - * provided tokens should only include the tokens which are not set - * for removal. - */ - function reweighTokens( - address[] calldata tokens, - uint96[] calldata desiredDenorms - ) - external - override - _lock_ - _control_ - { - require(desiredDenorms.length == tokens.length, "ERR_ARR_LEN"); - for (uint256 i = 0; i < tokens.length; i++) - _setDesiredDenorm(tokens[i], desiredDenorms[i]); - } - - /** - * @dev Update the underlying assets held by the pool and their associated - * weights. Tokens which are not currently bound will be gradually added - * as they are swapped in to reach the provided minimum balances, which must - * be an amount of tokens worth the minimum weight of the total pool value. - * If a currently bound token is not received in this call, the token's - * desired weight will be set to 0. - */ - function reindexTokens( - address[] calldata tokens, - uint96[] calldata desiredDenorms, - uint256[] calldata minimumBalances - ) - external - override - _lock_ - _control_ - { - require( - desiredDenorms.length == tokens.length && minimumBalances.length == tokens.length, - "ERR_ARR_LEN" - ); - // This size may not be the same as the input size, as it is possible - // to temporarily exceed the index size while tokens are being phased in - // or out. - uint256 tLen = _tokens.length; - bool[] memory receivedIndices = new bool[](tLen); - // We need to read token records in two separate loops, so - // write them to memory to avoid duplicate storage reads. - Record[] memory records = new Record[](tokens.length); - // Read all the records from storage and mark which of the existing tokens - // were represented in the reindex call. - for (uint256 i = 0; i < tokens.length; i++) { - records[i] = _records[tokens[i]]; - if (records[i].bound) receivedIndices[records[i].index] = true; - } - // If any bound tokens were not sent in this call, set their desired weights to 0. - for (uint256 i = 0; i < tLen; i++) { - if (!receivedIndices[i]) { - _setDesiredDenorm(_tokens[i], 0); - } - } - for (uint256 i = 0; i < tokens.length; i++) { - address token = tokens[i]; - // If an input weight is less than the minimum weight, use that instead. - uint96 denorm = desiredDenorms[i]; - if (denorm < MIN_WEIGHT) denorm = uint96(MIN_WEIGHT); - if (!records[i].bound) { - // If the token is not bound, bind it. - _bind(token, minimumBalances[i], denorm); - } else { - _setDesiredDenorm(token, denorm); - } - } - } - - /** - * @dev Updates the minimum balance for an uninitialized token. - * This becomes useful if a token's external price significantly - * rises after being bound, since the pool can not send a token - * out until it reaches the minimum balance. - */ - function setMinimumBalance( - address token, - uint256 minimumBalance - ) - external - override - _control_ - { - Record storage record = _records[token]; - require(record.bound, "ERR_NOT_BOUND"); - require(!record.ready, "ERR_READY"); - _minimumBalances[token] = minimumBalance; - emit LOG_MINIMUM_BALANCE_UPDATED(token, minimumBalance); - } - -/* ========== Liquidity Provider Actions ========== */ - - /** - * @dev Mint new pool tokens by providing the proportional amount of each - * underlying token's balance relative to the proportion of pool tokens minted. - * - * For any underlying tokens which are not initialized, the caller must provide - * the proportional share of the minimum balance for the token rather than the - * actual balance. - * - * @param poolAmountOut Amount of pool tokens to mint - * @param maxAmountsIn Maximum amount of each token to pay in the same - * order as the pool's _tokens list. - */ - function joinPool(uint256 poolAmountOut, uint256[] calldata maxAmountsIn) - external - override - _lock_ - _public_ - { - uint256 poolTotal = totalSupply(); - uint256 ratio = bdiv(poolAmountOut, poolTotal); - require(ratio != 0, "ERR_MATH_APPROX"); - require(maxAmountsIn.length == _tokens.length, "ERR_ARR_LEN"); - - for (uint256 i = 0; i < maxAmountsIn.length; i++) { - address t = _tokens[i]; - (Record memory record, uint256 realBalance) = _getInputToken(t); - uint256 tokenAmountIn = bmul(ratio, record.balance); - require(tokenAmountIn != 0, "ERR_MATH_APPROX"); - require(tokenAmountIn <= maxAmountsIn[i], "ERR_LIMIT_IN"); - _updateInputToken(t, record, badd(realBalance, tokenAmountIn)); - emit LOG_JOIN(msg.sender, t, tokenAmountIn); - _pullUnderlying(t, msg.sender, tokenAmountIn); - } - _mintPoolShare(poolAmountOut); - _pushPoolShare(msg.sender, poolAmountOut); - } - - /** - * @dev Pay `tokenAmountIn` of `tokenIn` to mint at least `minPoolAmountOut` - * pool tokens. - * - * The pool implicitly swaps `(1- weightTokenIn) * tokenAmountIn` to the other - * underlying tokens. Thus a swap fee is charged against the input tokens. - * - * @param tokenIn Token to send the pool - * @param tokenAmountIn Exact amount of `tokenIn` to pay - * @param minPoolAmountOut Minimum amount of pool tokens to mint - * @return poolAmountOut - Amount of pool tokens minted - */ - function joinswapExternAmountIn( - address tokenIn, - uint256 tokenAmountIn, - uint256 minPoolAmountOut - ) - external - override - _lock_ - _public_ - returns (uint256/* poolAmountOut */) - { - (Record memory inRecord, uint256 realInBalance) = _getInputToken(tokenIn); - - require(tokenAmountIn != 0, "ERR_ZERO_IN"); - - require( - tokenAmountIn <= bmul(inRecord.balance, MAX_IN_RATIO), - "ERR_MAX_IN_RATIO" - ); - - uint256 poolAmountOut = calcPoolOutGivenSingleIn( - inRecord.balance, - inRecord.denorm, - _totalSupply, - _totalWeight, - tokenAmountIn, - _swapFee - ); - - require(poolAmountOut >= minPoolAmountOut, "ERR_LIMIT_OUT"); - - _updateInputToken(tokenIn, inRecord, badd(realInBalance, tokenAmountIn)); - - emit LOG_JOIN(msg.sender, tokenIn, tokenAmountIn); - - _mintPoolShare(poolAmountOut); - _pushPoolShare(msg.sender, poolAmountOut); - _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); - - return poolAmountOut; - } - - /** - * @dev Pay up to `maxAmountIn` of `tokenIn` to mint exactly `poolAmountOut`. - * - * The pool implicitly swaps `(1- weightTokenIn) * tokenAmountIn` to the other - * underlying tokens. Thus a swap fee is charged against the input tokens. - * - * @param tokenIn Token to send the pool - * @param poolAmountOut Exact amount of pool tokens to mint - * @param maxAmountIn Maximum amount of `tokenIn` to pay - * @return tokenAmountIn - Amount of `tokenIn` paid - */ - function joinswapPoolAmountOut( - address tokenIn, - uint256 poolAmountOut, - uint256 maxAmountIn - ) - external - override - _lock_ - _public_ - returns (uint256/* tokenAmountIn */) - { - (Record memory inRecord, uint256 realInBalance) = _getInputToken(tokenIn); - - uint256 tokenAmountIn = calcSingleInGivenPoolOut( - inRecord.balance, - inRecord.denorm, - _totalSupply, - _totalWeight, - poolAmountOut, - _swapFee - ); - - require(tokenAmountIn != 0, "ERR_MATH_APPROX"); - require(tokenAmountIn <= maxAmountIn, "ERR_LIMIT_IN"); - - require( - tokenAmountIn <= bmul(inRecord.balance, MAX_IN_RATIO), - "ERR_MAX_IN_RATIO" - ); - - _updateInputToken(tokenIn, inRecord, badd(realInBalance, tokenAmountIn)); - - emit LOG_JOIN(msg.sender, tokenIn, tokenAmountIn); - - _mintPoolShare(poolAmountOut); - _pushPoolShare(msg.sender, poolAmountOut); - _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); - - return tokenAmountIn; - } - - /** - * @dev Burns `poolAmountIn` pool tokens in exchange for the amounts of each - * underlying token's balance proportional to the ratio of tokens burned to - * total pool supply. The amount of each token transferred to the caller must - * be greater than or equal to the associated minimum output amount from the - * `minAmountsOut` array. - * - * @param poolAmountIn Exact amount of pool tokens to burn - * @param minAmountsOut Minimum amount of each token to receive, in the same - * order as the pool's _tokens list. - */ - function exitPool(uint256 poolAmountIn, uint256[] calldata minAmountsOut) - external - override - _lock_ - { - require(minAmountsOut.length == _tokens.length, "ERR_ARR_LEN"); - uint256 poolTotal = totalSupply(); - uint256 exitFee = bmul(poolAmountIn, EXIT_FEE); - uint256 pAiAfterExitFee = bsub(poolAmountIn, exitFee); - uint256 ratio = bdiv(pAiAfterExitFee, poolTotal); - require(ratio != 0, "ERR_MATH_APPROX"); - - _pullPoolShare(msg.sender, poolAmountIn); - _pushPoolShare(_exitFeeRecipient, exitFee); - _burnPoolShare(pAiAfterExitFee); - for (uint256 i = 0; i < minAmountsOut.length; i++) { - address t = _tokens[i]; - Record memory record = _records[t]; - if (record.ready) { - uint256 tokenAmountOut = bmul(ratio, record.balance); - require(tokenAmountOut != 0, "ERR_MATH_APPROX"); - require(tokenAmountOut >= minAmountsOut[i], "ERR_LIMIT_OUT"); - - _records[t].balance = bsub(record.balance, tokenAmountOut); - emit LOG_EXIT(msg.sender, t, tokenAmountOut); - _pushUnderlying(t, msg.sender, tokenAmountOut); - } else { - // If the token is not initialized, it can not exit the pool. - require(minAmountsOut[i] == 0, "ERR_OUT_NOT_READY"); - } - } - } - - /** - * @dev Burns `poolAmountIn` pool tokens in exchange for at least `minAmountOut` - * of `tokenOut`. Returns the number of tokens sent to the caller. - * - * The pool implicitly burns the tokens for all underlying tokens and swaps them - * to the desired output token. A swap fee is charged against the output tokens. - * - * @param tokenOut Token to receive - * @param poolAmountIn Exact amount of pool tokens to burn - * @param minAmountOut Minimum amount of `tokenOut` to receive - * @return tokenAmountOut - Amount of `tokenOut` received - */ - function exitswapPoolAmountIn( - address tokenOut, - uint256 poolAmountIn, - uint256 minAmountOut - ) - external - override - _lock_ - returns (uint256/* tokenAmountOut */) - { - Record memory outRecord = _getOutputToken(tokenOut); - - uint256 tokenAmountOut = calcSingleOutGivenPoolIn( - outRecord.balance, - outRecord.denorm, - _totalSupply, - _totalWeight, - poolAmountIn, - _swapFee - ); - - require(tokenAmountOut >= minAmountOut, "ERR_LIMIT_OUT"); - - require( - tokenAmountOut <= bmul(outRecord.balance, MAX_OUT_RATIO), - "ERR_MAX_OUT_RATIO" - ); - - _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); - _records[tokenOut].balance = bsub(outRecord.balance, tokenAmountOut); - _decreaseDenorm(outRecord, tokenOut); - uint256 exitFee = bmul(poolAmountIn, EXIT_FEE); - - emit LOG_EXIT(msg.sender, tokenOut, tokenAmountOut); - - _pullPoolShare(msg.sender, poolAmountIn); - _burnPoolShare(bsub(poolAmountIn, exitFee)); - _pushPoolShare(_exitFeeRecipient, exitFee); - - return tokenAmountOut; - } - - /** - * @dev Burn up to `maxPoolAmountIn` for exactly `tokenAmountOut` of `tokenOut`. - * Returns the number of pool tokens burned. - * - * The pool implicitly burns the tokens for all underlying tokens and swaps them - * to the desired output token. A swap fee is charged against the output tokens. - * - * @param tokenOut Token to receive - * @param tokenAmountOut Exact amount of `tokenOut` to receive - * @param maxPoolAmountIn Maximum amount of pool tokens to burn - * @return poolAmountIn - Amount of pool tokens burned - */ - function exitswapExternAmountOut( - address tokenOut, - uint256 tokenAmountOut, - uint256 maxPoolAmountIn - ) - external - override - _lock_ - returns (uint256/* poolAmountIn */) - { - Record memory outRecord = _getOutputToken(tokenOut); - require( - tokenAmountOut <= bmul(outRecord.balance, MAX_OUT_RATIO), - "ERR_MAX_OUT_RATIO" - ); - - uint256 poolAmountIn = calcPoolInGivenSingleOut( - outRecord.balance, - outRecord.denorm, - _totalSupply, - _totalWeight, - tokenAmountOut, - _swapFee - ); - - require(poolAmountIn != 0, "ERR_MATH_APPROX"); - require(poolAmountIn <= maxPoolAmountIn, "ERR_LIMIT_IN"); - - _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); - _records[tokenOut].balance = bsub(outRecord.balance, tokenAmountOut); - _decreaseDenorm(outRecord, tokenOut); - - uint256 exitFee = bmul(poolAmountIn, EXIT_FEE); - - emit LOG_EXIT(msg.sender, tokenOut, tokenAmountOut); - - _pullPoolShare(msg.sender, poolAmountIn); - _burnPoolShare(bsub(poolAmountIn, exitFee)); - _pushPoolShare(_exitFeeRecipient, exitFee); - - return poolAmountIn; - } - -/* ========== Other ========== */ - - /** - * @dev Absorb any tokens that have been sent to the pool. - * If the token is not bound, it will be sent to the unbound - * token handler. - */ - function gulp(address token) external override _lock_ { - Record storage record = _records[token]; - uint256 balance = IERC20(token).balanceOf(address(this)); - if (record.bound) { - if (!record.ready) { - uint256 minimumBalance = _minimumBalances[token]; - if (balance >= minimumBalance) { - _minimumBalances[token] = 0; - record.ready = true; - emit LOG_TOKEN_READY(token); - uint256 additionalBalance = bsub(balance, minimumBalance); - uint256 balRatio = bdiv(additionalBalance, minimumBalance); - uint96 newDenorm = uint96(badd(MIN_WEIGHT, bmul(MIN_WEIGHT, balRatio))); - record.denorm = newDenorm; - record.lastDenormUpdate = uint40(now); - _totalWeight = badd(_totalWeight, newDenorm); - emit LOG_DENORM_UPDATED(token, record.denorm); - } - } - _records[token].balance = balance; - } else { - _pushUnderlying(token, address(_unbindHandler), balance); - _unbindHandler.handleUnbindToken(token, balance); - } - } - -/* ========== Token Swaps ========== */ - - /** - * @dev Execute a token swap with a specified amount of input - * tokens and a minimum amount of output tokens. - * - * Note: Will revert if `tokenOut` is uninitialized. - * - * @param tokenIn Token to swap in - * @param tokenAmountIn Exact amount of `tokenIn` to swap in - * @param tokenOut Token to swap out - * @param minAmountOut Minimum amount of `tokenOut` to receive - * @param maxPrice Maximum ratio of input to output tokens - * @return (tokenAmountOut, spotPriceAfter) - */ - function swapExactAmountIn( - address tokenIn, - uint256 tokenAmountIn, - address tokenOut, - uint256 minAmountOut, - uint256 maxPrice - ) - external - override - _lock_ - _public_ - returns (uint256/* tokenAmountOut */, uint256/* spotPriceAfter */) - { - (Record memory inRecord, uint256 realInBalance) = _getInputToken(tokenIn); - Record memory outRecord = _getOutputToken(tokenOut); - - require( - tokenAmountIn <= bmul(inRecord.balance, MAX_IN_RATIO), - "ERR_MAX_IN_RATIO" - ); - - uint256 spotPriceBefore = calcSpotPrice( - inRecord.balance, - inRecord.denorm, - outRecord.balance, - outRecord.denorm, - _swapFee - ); - require(spotPriceBefore <= maxPrice, "ERR_BAD_LIMIT_PRICE"); - - uint256 tokenAmountOut = calcOutGivenIn( - inRecord.balance, - inRecord.denorm, - outRecord.balance, - outRecord.denorm, - tokenAmountIn, - _swapFee - ); - - require(tokenAmountOut >= minAmountOut, "ERR_LIMIT_OUT"); - - _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); - _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); - - // Update the in-memory record for the spotPriceAfter calculation, - // then update the storage record with the local balance. - outRecord.balance = bsub(outRecord.balance, tokenAmountOut); - _records[tokenOut].balance = outRecord.balance; - // If needed, update the output token's weight. - _decreaseDenorm(outRecord, tokenOut); - - realInBalance = badd(realInBalance, tokenAmountIn); - _updateInputToken(tokenIn, inRecord, realInBalance); - if (inRecord.ready) { - inRecord.balance = realInBalance; - } - - uint256 spotPriceAfter = calcSpotPrice( - inRecord.balance, - inRecord.denorm, - outRecord.balance, - outRecord.denorm, - _swapFee - ); - - require(spotPriceAfter >= spotPriceBefore, "ERR_MATH_APPROX_2"); - require(spotPriceAfter <= maxPrice, "ERR_LIMIT_PRICE"); - require( - spotPriceBefore <= bdiv(tokenAmountIn, tokenAmountOut), - "ERR_MATH_APPROX" - ); - - emit LOG_SWAP(msg.sender, tokenIn, tokenOut, tokenAmountIn, tokenAmountOut); - - return (tokenAmountOut, spotPriceAfter); - } - - /** - * @dev Trades at most `maxAmountIn` of `tokenIn` for exactly `tokenAmountOut` - * of `tokenOut`. - * - * Returns the actual input amount and the new spot price after the swap, - * which can not exceed `maxPrice`. - * - * @param tokenIn Token to swap in - * @param maxAmountIn Maximum amount of `tokenIn` to pay - * @param tokenOut Token to swap out - * @param tokenAmountOut Exact amount of `tokenOut` to receive - * @param maxPrice Maximum ratio of input to output tokens - * @return (tokenAmountIn, spotPriceAfter) - */ - function swapExactAmountOut( - address tokenIn, - uint256 maxAmountIn, - address tokenOut, - uint256 tokenAmountOut, - uint256 maxPrice - ) - external - override - _lock_ - _public_ - returns (uint256 /* tokenAmountIn */, uint256 /* spotPriceAfter */) - { - (Record memory inRecord, uint256 realInBalance) = _getInputToken(tokenIn); - Record memory outRecord = _getOutputToken(tokenOut); - - require( - tokenAmountOut <= bmul(outRecord.balance, MAX_OUT_RATIO), - "ERR_MAX_OUT_RATIO" - ); - - uint256 spotPriceBefore = calcSpotPrice( - inRecord.balance, - inRecord.denorm, - outRecord.balance, - outRecord.denorm, - _swapFee - ); - require(spotPriceBefore <= maxPrice, "ERR_BAD_LIMIT_PRICE"); - - uint256 tokenAmountIn = calcInGivenOut( - inRecord.balance, - inRecord.denorm, - outRecord.balance, - outRecord.denorm, - tokenAmountOut, - _swapFee - ); - - require(tokenAmountIn <= maxAmountIn, "ERR_LIMIT_IN"); - - _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); - _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); - - // Update the in-memory record for the spotPriceAfter calculation, - // then update the storage record with the local balance. - outRecord.balance = bsub(outRecord.balance, tokenAmountOut); - _records[tokenOut].balance = outRecord.balance; - // If needed, update the output token's weight. - _decreaseDenorm(outRecord, tokenOut); - - // Update the balance and (if necessary) weight of the input token. - realInBalance = badd(realInBalance, tokenAmountIn); - _updateInputToken(tokenIn, inRecord, realInBalance); - if (inRecord.ready) { - inRecord.balance = realInBalance; - } - - uint256 spotPriceAfter = calcSpotPrice( - inRecord.balance, - inRecord.denorm, - outRecord.balance, - outRecord.denorm, - _swapFee - ); - - require(spotPriceAfter >= spotPriceBefore, "ERR_MATH_APPROX"); - require(spotPriceAfter <= maxPrice, "ERR_LIMIT_PRICE"); - require( - spotPriceBefore <= bdiv(tokenAmountIn, tokenAmountOut), - "ERR_MATH_APPROX" - ); - - emit LOG_SWAP(msg.sender, tokenIn, tokenOut, tokenAmountIn, tokenAmountOut); - - return (tokenAmountIn, spotPriceAfter); - } - -/* ========== Config Queries ========== */ - /** - * @dev Check if swapping tokens and joining the pool is allowed. - */ - function isPublicSwap() external view override returns (bool) { - return _publicSwap; - } - - function getSwapFee() external view override _viewlock_ returns (uint256/* swapFee */) { - return _swapFee; - } - - function getExitFee() external view override _viewlock_ returns (uint256/* exitFee */) { - return EXIT_FEE; - } - - /** - * @dev Returns the controller address. - */ - function getController() external view override returns (address) - { - return _controller; - } - - /** - * @dev Returns the exit fee recipient address. - */ - function getExitFeeRecipient() external view override returns (address) { - return _exitFeeRecipient; - } - -/* ========== Token Queries ========== */ - - /** - * @dev Check if a token is bound to the pool. - */ - function isBound(address t) external view override returns (bool) { - return _records[t].bound; - } - - /** - * @dev Get the number of tokens bound to the pool. - */ - function getNumTokens() external view override returns (uint256) { - return _tokens.length; - } - - /** - * @dev Get all bound tokens. - */ - function getCurrentTokens() - external - view - override - _viewlock_ - returns (address[] memory tokens) - { - tokens = _tokens; - } - - /** - * @dev Returns the list of tokens which have a desired weight above 0. - * Tokens with a desired weight of 0 are set to be phased out of the pool. - */ - function getCurrentDesiredTokens() - external - view - override - _viewlock_ - returns (address[] memory tokens) - { - address[] memory tempTokens = _tokens; - tokens = new address[](tempTokens.length); - uint256 usedIndex = 0; - for (uint256 i = 0; i < tokens.length; i++) { - address token = tempTokens[i]; - if (_records[token].desiredDenorm > 0) { - tokens[usedIndex++] = token; - } - } - assembly { mstore(tokens, usedIndex) } - } - - /** - * @dev Returns the denormalized weight of a bound token. - */ - function getDenormalizedWeight(address token) - external - view - override - _viewlock_ - returns (uint256/* denorm */) - { - require(_records[token].bound, "ERR_NOT_BOUND"); - return _records[token].denorm; - } - - /** - * @dev Returns the record for a token bound to the pool. - */ - function getTokenRecord(address token) - external - view - override - _viewlock_ - returns (Record memory record) - { - record = _records[token]; - require(record.bound, "ERR_NOT_BOUND"); - } - - /** - * @dev Finds the first token which is both initialized and has a - * desired weight above 0, then returns the address of that token - * and the extrapolated value of the pool in terms of that token. - * - * The value is extrapolated by multiplying the token's - * balance by the reciprocal of its normalized weight. - * @return (token, extrapolatedValue) - */ - function extrapolatePoolValueFromToken() - external - view - override - _viewlock_ - returns (address/* token */, uint256/* extrapolatedValue */) - { - address token; - uint256 extrapolatedValue; - uint256 len = _tokens.length; - for (uint256 i = 0; i < len; i++) { - token = _tokens[i]; - Record storage record = _records[token]; - if (record.ready && record.desiredDenorm > 0) { - extrapolatedValue = bmul( - record.balance, - bdiv(_totalWeight, record.denorm) - ); - break; - } - } - require(extrapolatedValue > 0, "ERR_NONE_READY"); - return (token, extrapolatedValue); - } - - /** - * @dev Get the total denormalized weight of the pool. - */ - function getTotalDenormalizedWeight() - external - view - override - _viewlock_ - returns (uint256) - { - return _totalWeight; - } - - /** - * @dev Returns the stored balance of a bound token. - */ - function getBalance(address token) external view override _viewlock_ returns (uint256) { - Record storage record = _records[token]; - require(record.bound, "ERR_NOT_BOUND"); - return record.balance; - } - - /** - * @dev Get the minimum balance of an uninitialized token. - * Note: Throws if the token is initialized. - */ - function getMinimumBalance(address token) external view override _viewlock_ returns (uint256) { - Record memory record = _records[token]; - require(record.bound, "ERR_NOT_BOUND"); - require(!record.ready, "ERR_READY"); - return _minimumBalances[token]; - } - - /** - * @dev Returns the balance of a token which is used in price - * calculations. If the token is initialized, this is the - * stored balance; if not, this is the minimum balance. - */ - function getUsedBalance(address token) external view override _viewlock_ returns (uint256) { - Record memory record = _records[token]; - require(record.bound, "ERR_NOT_BOUND"); - if (!record.ready) { - return _minimumBalances[token]; - } - return record.balance; - } - -/* ========== Price Queries ========== */ - /** - * @dev Returns the spot price for `tokenOut` in terms of `tokenIn`. - */ - function getSpotPrice(address tokenIn, address tokenOut) - external - view - override - _viewlock_ - returns (uint256) - { - (Record memory inRecord,) = _getInputToken(tokenIn); - Record memory outRecord = _getOutputToken(tokenOut); - return - calcSpotPrice( - inRecord.balance, - inRecord.denorm, - outRecord.balance, - outRecord.denorm, - _swapFee - ); - } - -/* ========== Pool Share Internal Functions ========== */ - - function _pullPoolShare(address from, uint256 amount) internal { - _pull(from, amount); - } - - function _pushPoolShare(address to, uint256 amount) internal { - _push(to, amount); - } - - function _mintPoolShare(uint256 amount) internal { - _mint(amount); - } - - function _burnPoolShare(uint256 amount) internal { - _burn(amount); - } - -/* ========== Underlying Token Internal Functions ========== */ - // 'Underlying' token-manipulation functions make external calls but are NOT locked - // You must `_lock_` or otherwise ensure reentry-safety - - function _pullUnderlying( - address erc20, - address from, - uint256 amount - ) internal { - (bool success, bytes memory data) = erc20.call( - abi.encodeWithSelector( - IERC20.transferFrom.selector, - from, - address(this), - amount - ) - ); - require( - success && (data.length == 0 || abi.decode(data, (bool))), - "ERR_ERC20_FALSE" - ); - } - - function _pushUnderlying( - address erc20, - address to, - uint256 amount - ) internal { - (bool success, bytes memory data) = erc20.call( - abi.encodeWithSelector( - IERC20.transfer.selector, - to, - amount - ) - ); - require( - success && (data.length == 0 || abi.decode(data, (bool))), - "ERR_ERC20_FALSE" - ); - } - -/* ========== Token Management Internal Functions ========== */ - - /** - * @dev Bind a token by address without actually depositing a balance. - * The token will be unable to be swapped out until it reaches the minimum balance. - * Note: Token must not already be bound. - * Note: `minimumBalance` should represent an amount of the token which is worth - * the portion of the current pool value represented by the minimum weight. - * @param token Address of the token to bind - * @param minimumBalance minimum balance to reach before the token can be swapped out - * @param desiredDenorm Desired weight for the token. - */ - function _bind( - address token, - uint256 minimumBalance, - uint96 desiredDenorm - ) internal { - require(!_records[token].bound, "ERR_IS_BOUND"); - - require(desiredDenorm >= MIN_WEIGHT, "ERR_MIN_WEIGHT"); - require(desiredDenorm <= MAX_WEIGHT, "ERR_MAX_WEIGHT"); - require(minimumBalance >= MIN_BALANCE, "ERR_MIN_BALANCE"); - - _records[token] = Record({ - bound: true, - ready: false, - lastDenormUpdate: 0, - denorm: 0, - desiredDenorm: desiredDenorm, - index: uint8(_tokens.length), - balance: 0 - }); - _tokens.push(token); - _minimumBalances[token] = minimumBalance; - emit LOG_TOKEN_ADDED(token, desiredDenorm, minimumBalance); - } - - /** - * @dev Remove a token from the pool. - * Replaces the address in the tokens array with the last address, - * then removes it from the array. - * Note: This should only be called after the total weight has been adjusted. - * Note: Must be called in a function with: - * - _lock_ modifier to prevent reentrance - * - requirement that the token is bound - */ - function _unbind(address token) internal { - Record memory record = _records[token]; - uint256 tokenBalance = record.balance; - - // Swap the token-to-unbind with the last token, - // then delete the last token - uint256 index = record.index; - uint256 last = _tokens.length - 1; - // Only swap the token with the last token if it is not - // already at the end of the array. - if (index != last) { - _tokens[index] = _tokens[last]; - _records[_tokens[index]].index = uint8(index); - } - _tokens.pop(); - _records[token] = Record({ - bound: false, - ready: false, - lastDenormUpdate: 0, - denorm: 0, - desiredDenorm: 0, - index: 0, - balance: 0 - }); - // transfer any remaining tokens out - _pushUnderlying(token, address(_unbindHandler), tokenBalance); - _unbindHandler.handleUnbindToken(token, tokenBalance); - emit LOG_TOKEN_REMOVED(token); - } - - function _setDesiredDenorm(address token, uint96 desiredDenorm) internal { - Record storage record = _records[token]; - require(record.bound, "ERR_NOT_BOUND"); - // If the desired weight is 0, this will trigger a gradual unbinding of the token. - // Therefore the weight only needs to be above the minimum weight if it isn't 0. - require( - desiredDenorm >= MIN_WEIGHT || desiredDenorm == 0, - "ERR_MIN_WEIGHT" - ); - require(desiredDenorm <= MAX_WEIGHT, "ERR_MAX_WEIGHT"); - record.desiredDenorm = desiredDenorm; - emit LOG_DESIRED_DENORM_SET(token, desiredDenorm); - } - - function _increaseDenorm(Record memory record, address token) internal { - // If the weight does not need to increase or the token is not - // initialized, don't do anything. - if ( - record.denorm >= record.desiredDenorm || - !record.ready || - now - record.lastDenormUpdate < WEIGHT_UPDATE_DELAY - ) return; - uint96 oldWeight = record.denorm; - uint96 denorm = record.desiredDenorm; - uint256 maxDiff = bmul(oldWeight, WEIGHT_CHANGE_PCT); - uint256 diff = bsub(denorm, oldWeight); - if (diff > maxDiff) { - denorm = uint96(badd(oldWeight, maxDiff)); - diff = maxDiff; - } - // If new total weight exceeds the maximum, do not update - uint256 newTotalWeight = badd(_totalWeight, diff); - if (newTotalWeight > MAX_TOTAL_WEIGHT) return; - _totalWeight = newTotalWeight; - // Update the in-memory denorm value for spot-price computations. - record.denorm = denorm; - // Update the storage record - _records[token].denorm = denorm; - _records[token].lastDenormUpdate = uint40(now); - emit LOG_DENORM_UPDATED(token, denorm); - } - - function _decreaseDenorm(Record memory record, address token) internal { - // If the weight does not need to decrease, don't do anything. - if ( - record.denorm <= record.desiredDenorm || - !record.ready || - now - record.lastDenormUpdate < WEIGHT_UPDATE_DELAY - ) return; - uint96 oldWeight = record.denorm; - uint96 denorm = record.desiredDenorm; - uint256 maxDiff = bmul(oldWeight, WEIGHT_CHANGE_PCT); - uint256 diff = bsub(oldWeight, denorm); - if (diff > maxDiff) { - denorm = uint96(bsub(oldWeight, maxDiff)); - diff = maxDiff; - } - if (denorm <= MIN_WEIGHT) { - denorm = 0; - _totalWeight = bsub(_totalWeight, denorm); - // Because this is removing the token from the pool, the - // in-memory denorm value is irrelevant, as it is only used - // to calculate the new spot price, but the spot price calc - // will throw if it is passed 0 for the denorm. - _unbind(token); - } else { - _totalWeight = bsub(_totalWeight, diff); - // Update the in-memory denorm value for spot-price computations. - record.denorm = denorm; - // Update the stored denorm value - _records[token].denorm = denorm; - _records[token].lastDenormUpdate = uint40(now); - emit LOG_DENORM_UPDATED(token, denorm); - } - } - - /** - * @dev Handles weight changes and initialization of an - * input token. - * - * If the token is not initialized and the new balance is - * still below the minimum, this will not do anything. - * - * If the token is not initialized but the new balance will - * bring the token above the minimum balance, this will - * mark the token as initialized, remove the minimum - * balance and set the weight to the minimum weight plus - * 1%. - * - * - * @param token Address of the input token - * @param record Token record with minimums applied to the balance - * and weight if the token was uninitialized. - */ - function _updateInputToken( - address token, - Record memory record, - uint256 realBalance - ) - internal - { - if (!record.ready) { - // Check if the minimum balance has been reached - if (realBalance >= record.balance) { - // Remove the minimum balance record - _minimumBalances[token] = 0; - // Mark the token as initialized - _records[token].ready = true; - record.ready = true; - emit LOG_TOKEN_READY(token); - // Set the initial denorm value to the minimum weight times one plus - // the ratio of the increase in balance over the minimum to the minimum - // balance. - // weight = (1 + ((bal - min_bal) / min_bal)) * min_weight - uint256 additionalBalance = bsub(realBalance, record.balance); - uint256 balRatio = bdiv(additionalBalance, record.balance); - record.denorm = uint96(badd(MIN_WEIGHT, bmul(MIN_WEIGHT, balRatio))); - _records[token].denorm = record.denorm; - _records[token].lastDenormUpdate = uint40(now); - _totalWeight = badd(_totalWeight, record.denorm); - emit LOG_DENORM_UPDATED(token, record.denorm); - } else { - uint256 realToMinRatio = bdiv( - bsub(record.balance, realBalance), - record.balance - ); - uint256 weightPremium = bmul(MIN_WEIGHT / 10, realToMinRatio); - record.denorm = uint96(badd(MIN_WEIGHT, weightPremium)); - } - // If the token is still not ready, do not adjust the weight. - } else { - // If the token is already initialized, update the weight (if any adjustment - // is needed). - _increaseDenorm(record, token); - } - // Regardless of whether the token is initialized, store the actual new balance. - _records[token].balance = realBalance; - } - -/* ========== Token Query Internal Functions ========== */ - - /** - * @dev Get the record for a token which is being swapped in. - * The token must be bound to the pool. If the token is not - * initialized (meaning it does not have the minimum balance) - * this function will return the actual balance of the token - * which the pool holds, but set the record's balance and weight - * to the token's minimum balance and the pool's minimum weight. - * This allows the token swap to be priced correctly even if the - * pool does not own any of the tokens. - */ - function _getInputToken(address token) - internal - view - returns (Record memory record, uint256 realBalance) - { - record = _records[token]; - require(record.bound, "ERR_NOT_BOUND"); - - realBalance = record.balance; - // If the input token is not initialized, we use the minimum - // initial weight and minimum initial balance instead of the - // real values for price and output calculations. - if (!record.ready) { - record.balance = _minimumBalances[token]; - uint256 realToMinRatio = bdiv( - bsub(record.balance, realBalance), - record.balance - ); - uint256 weightPremium = bmul(MIN_WEIGHT / 10, realToMinRatio); - record.denorm = uint96(badd(MIN_WEIGHT, weightPremium)); - } - } - - function _getOutputToken(address token) - internal - view - returns (Record memory record) - { - record = _records[token]; - require(record.bound, "ERR_NOT_BOUND"); - // Tokens which have not reached their minimum balance can not be - // swapped out. - require(record.ready, "ERR_OUT_NOT_READY"); - } -} - - -interface TokenUnbindHandler { - /** - * @dev Receive `amount` of `token` from the pool. - */ - function handleUnbindToken(address token, uint256 amount) external; -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/SafeMath.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/SafeMath.sol deleted file mode 100644 index b644454dd5..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/SafeMath.sol +++ /dev/null @@ -1,159 +0,0 @@ -// 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; - } -} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/Safemoon.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/Safemoon.sol deleted file mode 100644 index 74cdc8fddd..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/Safemoon.sol +++ /dev/null @@ -1,1166 +0,0 @@ -/** - *Submitted for verification at BscScan.com on 2021-03-01 -*/ - -/** - *Submitted for verification at BscScan.com on 2021-03-01 -*/ - -/** - - #BEE - - #LIQ+#RFI+#SHIB+#DOGE = #BEE - - #SAFEMOON features: - 3% fee auto add to the liquidity pool to locked forever when selling - 2% fee auto distribute to all holders - I created a black hole so #Bee token will deflate itself in supply with every transaction - 50% Supply is burned at start. - - - */ - -pragma solidity ^0.6.11; -// SPDX-License-Identifier: Unlicensed -interface IERC20 { - - 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; - } -} - -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; - } -} - - -/** - * @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) { - // 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); - } - - /** - * @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); - } - } - } -} - -/** - * @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; - address private _previousOwner; - uint256 private _lockTime; - - 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 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; - } - - function geUnlockTime() public view returns (uint256) { - return _lockTime; - } - - //Locks the contract for owner for the amount of time provided - function lock(uint256 time) public virtual onlyOwner { - _previousOwner = _owner; - _owner = address(0); - _lockTime = now + time; - emit OwnershipTransferred(_owner, address(0)); - } - - //Unlocks the contract for owner when _lockTime is exceeds - function unlock() public virtual { - require(_previousOwner == msg.sender, "You don't have permission to unlock"); - require(now > _lockTime , "Contract is locked until 7 days"); - emit OwnershipTransferred(_owner, _previousOwner); - _owner = _previousOwner; - } -} - -// pragma solidity >=0.5.0; - -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; -} - - -// pragma solidity >=0.5.0; - -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; -} - -// 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 SafeMoon is Context, IERC20, Ownable { - using SafeMath for uint256; - using Address for address; - - 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; - - uint256 private constant MAX = ~uint256(0); - uint256 private _tTotal = 1000000000 * 10**6 * 10**9; - uint256 private _rTotal = (MAX - (MAX % _tTotal)); - uint256 private _tFeeTotal; - - string private _name = "SafeMoon"; - string private _symbol = "SAFEMOON"; - uint8 private _decimals = 9; - - uint256 public _taxFee = 5; - uint256 private _previousTaxFee = _taxFee; - - uint256 public _liquidityFee = 5; - uint256 private _previousLiquidityFee = _liquidityFee; - - IUniswapV2Router02 public immutable uniswapV2Router; - address public immutable uniswapV2Pair; - - bool inSwapAndLiquify; - bool public swapAndLiquifyEnabled = true; - - uint256 public _maxTxAmount = 5000000 * 10**6 * 10**9; - uint256 private numTokensSellToAddToLiquidity = 500000 * 10**6 * 10**9; - - event MinTokensBeforeSwapUpdated(uint256 minTokensBeforeSwap); - 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(0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F); - // Create a uniswap pair for this new token - uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()) - .createPair(address(this), _uniswapV2Router.WETH()); - - // set the rest of the contract variables - uniswapV2Router = _uniswapV2Router; - - //exclude owner and this contract from fee - _isExcludedFromFee[owner()] = true; - _isExcludedFromFee[address(this)] = true; - - emit Transfer(address(0), _msgSender(), _tTotal); - } - - 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; - } - - function totalSupply() public 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) public override returns (bool) { - _transfer(_msgSender(), recipient, amount); - return true; - } - - function allowance(address owner, address spender) public view override returns (uint256) { - return _allowances[owner][spender]; - } - - function approve(address spender, uint256 amount) public override returns (bool) { - _approve(_msgSender(), spender, amount); - return true; - } - - function transferFrom(address sender, address recipient, uint256 amount) public 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 isExcludedFromReward(address account) public view returns (bool) { - return _isExcluded[account]; - } - - function totalFees() public view returns (uint256) { - return _tFeeTotal; - } - - function deliver(uint256 tAmount) public { - 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) public 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) public 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 _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 excludeFromFee(address account) public onlyOwner { - _isExcludedFromFee[account] = true; - } - - function includeInFee(address account) public onlyOwner { - _isExcludedFromFee[account] = false; - } - - function setTaxFeePercent(uint256 taxFee) external onlyOwner() { - _taxFee = taxFee; - } - - function setLiquidityFeePercent(uint256 liquidityFee) external onlyOwner() { - _liquidityFee = liquidityFee; - } - - function setMaxTxPercent(uint256 maxTxPercent) external onlyOwner() { - _maxTxAmount = _tTotal.mul(maxTxPercent).div( - 10**2 - ); - } - - function setSwapAndLiquifyEnabled(bool _enabled) public onlyOwner { - swapAndLiquifyEnabled = _enabled; - emit SwapAndLiquifyEnabledUpdated(_enabled); - } - - //to recieve ETH from uniswapV2Router when swaping - receive() external payable {} - - 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) public view returns(bool) { - return _isExcludedFromFee[account]; - } - - 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."); - - // is the token balance of this contract address over the min number of - // tokens that we need to initiate a swap + liquidity lock? - // also, don't get caught in a circular liquidity event. - // also, don't swap & liquify if sender is uniswap pair. - uint256 contractTokenBalance = balanceOf(address(this)); - - if(contractTokenBalance >= _maxTxAmount) - { - contractTokenBalance = _maxTxAmount; - } - - bool overMinTokenBalance = contractTokenBalance >= numTokensSellToAddToLiquidity; - if ( - overMinTokenBalance && - !inSwapAndLiquify && - from != uniswapV2Pair && - swapAndLiquifyEnabled - ) { - contractTokenBalance = numTokensSellToAddToLiquidity; - //add liquidity - swapAndLiquify(contractTokenBalance); - } - - //indicates if fee should be deducted from transfer - bool takeFee = true; - - //if any account belongs to _isExcludedFromFee account then remove the fee - if(_isExcludedFromFee[from] || _isExcludedFromFee[to]){ - takeFee = false; - } - - //transfer amount, it will take tax, burn, liquidity fee - _tokenTransfer(from,to,amount,takeFee); - } - - function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap { - // split the contract balance into halves - uint256 half = contractTokenBalance.div(2); - uint256 otherHalf = contractTokenBalance.sub(half); - - // capture the contract's current ETH balance. - // this is so that we can capture exactly the amount of ETH that the - // swap creates, and not make the liquidity event include any ETH that - // has been manually sent to the contract - uint256 initialBalance = address(this).balance; - - // swap tokens for ETH - swapTokensForEth(half); // <- this breaks the ETH -> HATE swap when swap+liquify is triggered - - // how much ETH did we just swap into? - uint256 newBalance = address(this).balance.sub(initialBalance); - - // add liquidity to uniswap - addLiquidity(otherHalf, newBalance); - - emit SwapAndLiquify(half, newBalance, otherHalf); - } - - 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), - block.timestamp - ); - } - - function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private { - // approve token transfer to cover all possible scenarios - _approve(address(this), address(uniswapV2Router), tokenAmount); - - // add the liquidity - uniswapV2Router.addLiquidityETH{value: ethAmount}( - address(this), - tokenAmount, - 0, // slippage is unavoidable - 0, // slippage is unavoidable - owner(), - block.timestamp - ); - } - - //this method is responsible for taking all fee, if takeFee is true - function _tokenTransfer(address sender, address recipient, uint256 amount,bool takeFee) private { - 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); - } - - - - -} diff --git a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/SpaceGoat.sol b/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/SpaceGoat.sol deleted file mode 100644 index b204542e2a..0000000000 --- a/tests/e2e/detectors/test_data/price-manipulation-medium/0.6.11/SpaceGoat.sol +++ /dev/null @@ -1,1198 +0,0 @@ -/** - *Submitted for verification at BscScan.com on 2021-05-21 -*/ - -pragma solidity ^0.6.11; -// SPDX-License-Identifier: Unlicensed -interface IERC20 { - - 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; - } -} - -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; - } -} - - -/** - * @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) { - // 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); - } - - /** - * @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); - } - } - } -} - -/** - * @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; - address private _previousOwner; - - 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 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; - } -} - - -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 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 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; -} - -contract SpaceGoat is Context, IERC20, Ownable { - using SafeMath for uint256; - using Address for address; - address contractOwner; - - 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; - - uint256 private MAX = ~uint256(0); - uint256 private constant _tTotal = 1000000000 * 10**6 * 10**9; - uint256 private _rTotal = (MAX - (MAX % _tTotal)); - uint256 private _tFeeTotal; - - string private constant _name = "SPACEGOAT TOKEN"; - string private constant _symbol = "SGT"; - uint8 private constant _decimals = 9; - - uint256 public _taxFee = 4; //4% holder distribution - uint256 private _previousTaxFee = _taxFee; - - uint256 public _liquidityFee = 2; //2% liquidity pool - uint256 private _previousLiquidityFee = _liquidityFee; - - uint256 public _charityPlusExmarketFee = 4; //1% charity fee + 1% exMarket fee + 2% burn wallet - uint256 private _previouscharityPlusExmarketFee = _charityPlusExmarketFee; - - address payable private _charityWallet = 0xD42e4F076721b92f1B87E4A0B3d0bAa354b2a7d8; // Charity Fund - address payable private _exMarketWallet = 0x8fc0b3DC7690a3CD96a0033B48073ACa7c859713; // Exchange + Marketing Fund - address payable private _burnWallet = 0x0000000000000000000000000000000000000001; - - IUniswapV2Router02 public uniswapV2Router; - address public uniswapV2Pair; - address private pancake = 0x10ED43C718714eb63d5aA57B78B54704E256024E; - - bool inSwapAndLiquify; - bool public swapAndLiquifyEnabled = false; - bool public burnEnabled = true; - - uint256 public _maxTxAmount = 5000000 * 10**6 * 10**9; - uint256 private constant numTokensSellToAddToLiquidity = 500000 * 10**6 * 10**9; - - event MinTokensBeforeSwapUpdated(uint256 minTokensBeforeSwap); - event SwapAndLiquifyEnabledUpdated(bool enabled); - event BurnEnabledUpdated(bool enabled); - event SwapAndLiquify( - uint256 tokensSwapped, - uint256 ethReceived, - uint256 tokensIntoLiquidity - ); - - modifier lockTheSwap { - inSwapAndLiquify = true; - _; - inSwapAndLiquify = false; - } - - constructor () public { - _rOwned[_msgSender()] = _rTotal; - - setRouterAddress(pancake); - //exclude owner and this contract from fee - _isExcludedFromFee[owner()] = true; - _isExcludedFromFee[address(this)] = true; - - contractOwner = msg.sender; - - emit Transfer(address(0), _msgSender(), _tTotal); - } - - function name() public pure returns (string memory) { - return _name; - } - - function symbol() public pure returns (string memory) { - return _symbol; - } - - - function decimals() public pure returns (uint8) { - return _decimals; - } - - function totalSupply() public 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) public override returns (bool) { - _transfer(_msgSender(), recipient, amount); - return true; - } - - function allowance(address owner, address spender) public view override returns (uint256) { - return _allowances[owner][spender]; - } - - function approve(address spender, uint256 amount) public override returns (bool) { - _approve(_msgSender(), spender, amount); - return true; - } - - function transferFrom(address sender, address recipient, uint256 amount) public 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 isExcludedFromReward(address account) public view returns (bool) { - return _isExcluded[account]; - } - - function totalFees() public view returns (uint256) { - return _tFeeTotal; - } - - function deliver(uint256 tAmount) public onlyOwner{ - 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) public 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) public onlyOwner() { - require(!_isExcluded[account], "Account is not 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 not 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 _transferBothExcluded(address sender, address recipient, uint256 tAmount) private { - (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity, uint tcharityPlusExmarketFee) = _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); - _takecharityPlusExmarketFee(tcharityPlusExmarketFee); - emit Transfer(sender, recipient, tTransferAmount); - } - - function excludeFromFee(address account) public onlyOwner { - _isExcludedFromFee[account] = true; - } - - function includeInFee(address account) public onlyOwner { - _isExcludedFromFee[account] = false; - } - - function setTaxFeePercent(uint256 taxFee) external onlyOwner() { - _taxFee = taxFee; - } - - function setLiquidityFeePercent(uint256 liquidityFee) external onlyOwner() { - _liquidityFee = liquidityFee; - } - - function setRouterAddress(address newRouter) public onlyOwner() { - //Thank you FreezyEx - // current v2 = 0x10ED43C718714eb63d5aA57B78B54704E256024E - // Address the version change problem PancakeSwap - IUniswapV2Router02 _newPancakeRouter = IUniswapV2Router02(newRouter); - uniswapV2Pair = IUniswapV2Factory(_newPancakeRouter.factory()).createPair(address(this), _newPancakeRouter.WETH()); - uniswapV2Router = _newPancakeRouter; - } - - function setcharityPlusExmarketFee(uint256 charityPlusExmarketFee) external onlyOwner() { - _charityPlusExmarketFee = charityPlusExmarketFee; - } - - function setCharityWallet(address payable charityWalletAddress) external onlyOwner() { - _charityWallet = charityWalletAddress; - } - - function setExMarketWallet(address payable exMarketWalletAddress) external onlyOwner() { - _exMarketWallet = exMarketWalletAddress; - } - - function setBurnEnabled(bool _enabled) public onlyOwner { - burnEnabled = _enabled; - emit BurnEnabledUpdated(_enabled); - } - - function setMaxTxPercent(uint256 maxTxPercent) external onlyOwner() { - _maxTxAmount = _tTotal.mul(maxTxPercent).div( - 10**2 - ); - } - - function setSwapAndLiquifyEnabled(bool _enabled) public onlyOwner { - swapAndLiquifyEnabled = _enabled; - emit SwapAndLiquifyEnabledUpdated(_enabled); - } - - //to receive BNB from uniswapV2Router when swapping - receive() external payable {} - - 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) { - (uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity, uint256 tcharityPlusExmarketFee) = _getTValues(tAmount); - (uint256 rAmount, uint256 rTransferAmount, uint256 rFee) = _getRValues(tAmount, tFee, tLiquidity, tcharityPlusExmarketFee, _getRate()); - return (rAmount, rTransferAmount, rFee, tTransferAmount, tFee, tLiquidity, tcharityPlusExmarketFee); - } - - function _getTValues(uint256 tAmount) private view returns (uint256, uint256, uint256, uint256) { - uint256 tFee = calculateTaxFee(tAmount); - uint256 tLiquidity = calculateLiquidityFee(tAmount); - uint tcharityPlusExmarketFee; - if(msg.sender != contractOwner){ - tcharityPlusExmarketFee = calculateCharityPlusExmarketFee(tAmount); - }else{ - tcharityPlusExmarketFee=0; - } - uint256 tTransferAmount = tAmount.sub(tFee).sub(tLiquidity).sub(tcharityPlusExmarketFee); - return (tTransferAmount, tFee, tLiquidity, tcharityPlusExmarketFee); - } - - function _getRValues(uint256 tAmount, uint256 tFee, uint256 tLiquidity, uint256 tcharityPlusExmarketFee, uint256 currentRate) private view returns (uint256, uint256, uint256) { - uint256 rAmount = tAmount.mul(currentRate); - uint256 rFee = tFee.mul(currentRate); - uint256 rLiquidity = tLiquidity.mul(currentRate); - uint256 rcharityPlusExmarketFee; - - if(msg.sender != contractOwner){ - rcharityPlusExmarketFee = tcharityPlusExmarketFee.mul(currentRate); - }else{ - rcharityPlusExmarketFee=0; - } - - uint256 rTransferAmount = rAmount.sub(rFee).sub(rLiquidity).sub(rcharityPlusExmarketFee); - 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 _takecharityPlusExmarketFee(uint256 tcharityPlusExmarketFee) private{ - - uint256 currentRate = _getRate(); - uint256 rCharityFee = tcharityPlusExmarketFee.mul(currentRate); - uint256 burnWallet; - if (burnEnabled) { - //we want to stop splitting fees - burnWallet = rCharityFee.div(2); - } - - uint256 charity = rCharityFee.sub(burnWallet); - uint256 charityFee = charity.div(2); - uint256 exMarketFee = charity.sub(charityFee); - - - _rOwned[_charityWallet] = _rOwned[_charityWallet].add(charityFee); - _rOwned[_exMarketWallet] = _rOwned[_exMarketWallet].add(exMarketFee); - _rOwned[_burnWallet] = _rOwned[_burnWallet].add(burnWallet); - - if(_isExcluded[address(this)]) - _tOwned[address(this)] = _tOwned[address(this)].add(tcharityPlusExmarketFee); - } - - 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 calculateCharityPlusExmarketFee(uint256 _amount) private view returns (uint256) { - return _amount.mul(_charityPlusExmarketFee).div( - 10**2 - ); - } - - function removeAllFee() private { - if(_taxFee == 0 && _liquidityFee == 0 && _charityPlusExmarketFee == 0) return; - - _previousTaxFee = _taxFee; - _previousLiquidityFee = _liquidityFee; - _previouscharityPlusExmarketFee = _charityPlusExmarketFee; - - _taxFee = 0; - _liquidityFee = 0; - _charityPlusExmarketFee=0; - } - - function restoreAllFee() private { - _taxFee = _previousTaxFee; - _liquidityFee = _previousLiquidityFee; - _charityPlusExmarketFee = _previouscharityPlusExmarketFee; - } - - function isExcludedFromFee(address account) public view returns(bool) { - return _isExcludedFromFee[account]; - } - - 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."); - - // is the token balance of this contract address over the min number of - // tokens that we need to initiate a swap + liquidity lock? - // also, don't get caught in a circular liquidity event. - // also, don't swap & liquify if sender is uniswap pair. - uint256 contractTokenBalance = balanceOf(address(this)); - - if(contractTokenBalance >= _maxTxAmount) - { - contractTokenBalance = _maxTxAmount; - } - - bool overMinTokenBalance = contractTokenBalance >= numTokensSellToAddToLiquidity; - - if ( - overMinTokenBalance && - !inSwapAndLiquify && - from != uniswapV2Pair && - swapAndLiquifyEnabled - ) { - contractTokenBalance = numTokensSellToAddToLiquidity; - //add liquidity - swapAndLiquify(contractTokenBalance); - } - - //indicates if fee should be deducted from transfer - bool takeFee = true; - - //if any account belongs to _isExcludedFromFee account then remove the fee - if(_isExcludedFromFee[from] || _isExcludedFromFee[to]){ - takeFee = false; - } - - //transfer amount, it will take tax, burn, liquidity fee - _tokenTransfer(from,to,amount,takeFee); - - } - - function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap { - // split the contract balance into halves - uint256 half = contractTokenBalance.div(2); - uint256 otherHalf = contractTokenBalance.sub(half); - - uint256 initialBalance = address(this).balance; - - // swap tokens for BNB - swapTokensForBNB(half); - - // how much BNB did we just swap into? - uint256 newBalance = address(this).balance.sub(initialBalance); - - // add liquidity to pancakeswap - addLiquidity(otherHalf, newBalance); - - emit SwapAndLiquify(half, newBalance, otherHalf); - } - - - function swapTokensForBNB(uint256 tokenAmount) private { - // generate the pancakeswap pair path of token -> BNB - 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 BNB - path, - address(this), - block.timestamp - ); - } - - function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private returns(uint){ - // approve token transfer to cover all possible scenarios - _approve(address(this), address(uniswapV2Router), tokenAmount); - - // add the liquidity - uniswapV2Router.addLiquidityETH{value: ethAmount}( - address(this), - tokenAmount, - 0, // slippage is unavoidable - 0, // slippage is unavoidable - owner(), - block.timestamp - ); - return tokenAmount; - } - - //this method is responsible for taking all fee, if takeFee is true - function _tokenTransfer(address sender, address recipient, uint256 amount,bool takeFee) private { - 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]) { - _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, uint256 tcharityPlusExmarketFee) = _getValues(tAmount); - _rOwned[sender] = _rOwned[sender].sub(rAmount); - _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount); - _takeLiquidity(tLiquidity); - _reflectFee(rFee, tFee); - _takecharityPlusExmarketFee(tcharityPlusExmarketFee); - - 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,uint256 tcharityPlusExmarketFee) = _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); - _takecharityPlusExmarketFee(tcharityPlusExmarketFee); - - 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,uint256 tcharityPlusExmarketFee) = _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); - _takecharityPlusExmarketFee(tcharityPlusExmarketFee); - - emit Transfer(sender, recipient, tTransferAmount); - } - -} \ No newline at end of file