diff --git a/.solhint.json b/.solhint.json new file mode 100644 index 0000000..25f4f23 --- /dev/null +++ b/.solhint.json @@ -0,0 +1,63 @@ +{ + "extends": "solhint:recommended", + "rules": { + "no-console": "warn", + "avoid-low-level-calls": "off", + "code-complexity": ["error", 9], + "compiler-version": ["error", ">=0.8.0 <0.9.0"], + "const-name-snakecase": "off", + "gas-custom-errors": "off", + "func-name-mixedcase": "error", + "immutable-vars-naming": "error", + "func-visibility": ["error", { "ignoreConstructors": true }], + "max-line-length": ["error", 123], + "named-parameters-mapping": "warn", + "no-global-import": "warn", + "imports-order": "off", + "no-unused-import": "error", + "avoid-call-value": "error", + "avoid-low-level-calls": "error", + "no-complex-fallback": "error", + "avoid-tx-origin": "error", + "check-send-result": "error", + "compiler-version": "error", + "no-inline-assembly": "error", + "not-rely-on-block-hash": "error", + "reentrancy": "error", + "modifier-name-mixedcase": "error", + "state-visibility": "error", + "multiple-sends": "error", + "named-parameters-mapping": "error", + "visibility-modifier-order": "warn", + "gas-calldata-parameters": "warn", + "quotes": "warn", + "gas-indexed-events": "off", + "gas-strict-inequalities": "off", + "gas-struct-packing": "error", + "gas-increment-by-one": "warn", + "gas-length-in-loops": "off", + "no-unused-vars": "error", + "no-empty-blocks": "off", + "not-rely-on-time": "off", + "one-contract-per-file": "off", + "var-name-mixedcase": "warn", + "func-order": "off", + "max-line-length": "off", + "reason-string": "off", + "func-named-parameters": "error", + "imports-on-top": "error", + "ordering": "off", + "func-param-name-mixedcase": "error", + "modifier-name-mixedcase": "error", + "private-vars-leading-underscore": "error", + "use-forbidden-name": "error", + "code-complexity": "error", + "explicit-types": "error", + "constructor-syntax": "error", + "interface-starts-with-i": "error", + "const-name-snakecase": "error", + "contract-name-camelcase": "error", + "event-name-camelcase": "error", + "payable-fallback": "error" + } +} diff --git a/mainnet-contracts/.semgrepignore b/mainnet-contracts/.semgrepignore new file mode 100644 index 0000000..e192558 --- /dev/null +++ b/mainnet-contracts/.semgrepignore @@ -0,0 +1 @@ +test/* \ No newline at end of file diff --git a/mainnet-contracts/script/DeployFWR.s.sol b/mainnet-contracts/script/DeployFWR.s.sol index 56ec220..293e5fa 100644 --- a/mainnet-contracts/script/DeployFWR.s.sol +++ b/mainnet-contracts/script/DeployFWR.s.sol @@ -85,7 +85,7 @@ contract DeployFWR is DeployerHelper { // L1RewardManager L1RewardManager l1ReeardManagerImpl = new L1RewardManager({ - XpufETH: _getXPufETH(), + xPufETH: _getXPufETH(), pufETH: _getPufferVault(), lockbox: _getLockbox(), l2RewardsManager: l2RewardManagerProxy diff --git a/mainnet-contracts/script/DeployPufETHBridging.s.sol b/mainnet-contracts/script/DeployPufETHBridging.s.sol index 58a2dc5..32ce947 100644 --- a/mainnet-contracts/script/DeployPufETHBridging.s.sol +++ b/mainnet-contracts/script/DeployPufETHBridging.s.sol @@ -64,7 +64,7 @@ contract DeployPufETHBridging is BaseScript { // L1RewardManager L1RewardManager l1RewardManagerImpl = new L1RewardManager({ - XpufETH: address(xPufETHProxy), + xPufETH: address(xPufETHProxy), pufETH: deployment.pufferVault, lockbox: address(xERC20Lockbox), l2RewardsManager: address(l2RewardsManagerProxy) diff --git a/mainnet-contracts/script/UpgradeL2RewardManager.s.sol b/mainnet-contracts/script/UpgradeL2RewardManager.s.sol index 1fa94bf..1f3120c 100644 --- a/mainnet-contracts/script/UpgradeL2RewardManager.s.sol +++ b/mainnet-contracts/script/UpgradeL2RewardManager.s.sol @@ -36,7 +36,7 @@ contract UpgradeL2RewardManager is DeployerHelper { // L1RewardManager L1RewardManager l1RewardManagerImpl = new L1RewardManager({ - XpufETH: _getXPufETH(), + xPufETH: _getXPufETH(), pufETH: _getPufferVault(), lockbox: _getLockbox(), l2RewardsManager: l2RewardsManagerProxy diff --git a/mainnet-contracts/src/GuardianModule.sol b/mainnet-contracts/src/GuardianModule.sol index 8c981d9..fec650c 100644 --- a/mainnet-contracts/src/GuardianModule.sol +++ b/mainnet-contracts/src/GuardianModule.sol @@ -142,7 +142,7 @@ contract GuardianModule is AccessManaged, IGuardianModule { */ function validateProvisionNode( uint256 pufferModuleIndex, - bytes memory pubKey, + bytes calldata pubKey, bytes calldata signature, bytes calldata withdrawalCredentials, bytes32 depositDataRoot, diff --git a/mainnet-contracts/src/L1RewardManager.sol b/mainnet-contracts/src/L1RewardManager.sol index 0776f68..ab57d7d 100644 --- a/mainnet-contracts/src/L1RewardManager.sol +++ b/mainnet-contracts/src/L1RewardManager.sol @@ -50,8 +50,8 @@ contract L1RewardManager is /** * @custom:oz-upgrades-unsafe-allow constructor */ - constructor(address XpufETH, address lockbox, address pufETH, address l2RewardsManager) { - XPUFETH = IERC20(XpufETH); + constructor(address xPufETH, address lockbox, address pufETH, address l2RewardsManager) { + XPUFETH = IERC20(xPufETH); LOCKBOX = IXERC20Lockbox(lockbox); PUFFER_VAULT = PufferVaultV3(payable(pufETH)); L2_REWARDS_MANAGER = l2RewardsManager; @@ -168,7 +168,7 @@ contract L1RewardManager is * @notice This contract receives XPufETH from the L2RewardManager via the bridge, unwraps it to pufETH and then burns the pufETH, reverting the original mintAndBridge call * @dev Restricted access to `ROLE_ID_BRIDGE` */ - function xReceive(bytes32, uint256, address, address originSender, uint32 originDomainId, bytes memory callData) + function xReceive(bytes32, uint256, address, address originSender, uint32 originDomainId, bytes calldata callData) external override(IXReceiver) restricted @@ -212,7 +212,7 @@ contract L1RewardManager is * @param bridgeData The updated bridge data. * @dev Restricted access to `ROLE_ID_DAO` */ - function updateBridgeData(address bridge, BridgeData memory bridgeData) external restricted { + function updateBridgeData(address bridge, BridgeData calldata bridgeData) external restricted { RewardManagerStorage storage $ = _getRewardManagerStorage(); if (bridge == address(0)) { revert InvalidAddress(); diff --git a/mainnet-contracts/src/LibBeaconchainContract.sol b/mainnet-contracts/src/LibBeaconchainContract.sol index d04d106..3673fb4 100644 --- a/mainnet-contracts/src/LibBeaconchainContract.sol +++ b/mainnet-contracts/src/LibBeaconchainContract.sol @@ -16,7 +16,7 @@ library LibBeaconchainContract { * @param withdrawalCredentials is the withdrawal credentials * @return the deposit data root */ - function getDepositDataRoot(bytes memory pubKey, bytes calldata signature, bytes memory withdrawalCredentials) + function getDepositDataRoot(bytes calldata pubKey, bytes calldata signature, bytes calldata withdrawalCredentials) external pure returns (bytes32) diff --git a/mainnet-contracts/src/NoImplementation.sol b/mainnet-contracts/src/NoImplementation.sol index 6a88416..bdd73a8 100644 --- a/mainnet-contracts/src/NoImplementation.sol +++ b/mainnet-contracts/src/NoImplementation.sol @@ -2,17 +2,16 @@ pragma solidity >=0.8.0 <0.9.0; import { UUPSUpgradeable } from "@openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { Unauthorized } from "./Errors.sol"; contract NoImplementation is UUPSUpgradeable { - address immutable upgrader; + address public immutable UPGRADER; constructor() { - upgrader = msg.sender; + UPGRADER = msg.sender; } function _authorizeUpgrade(address) internal virtual override { - // solhint-disable-next-line custom-errors - require(msg.sender == upgrader, "Unauthorized"); - // anybody can steal this proxy + require(msg.sender == UPGRADER, Unauthorized()); } } diff --git a/mainnet-contracts/src/PufLocker.sol b/mainnet-contracts/src/PufLocker.sol index f49d4cd..2cffdc6 100644 --- a/mainnet-contracts/src/PufLocker.sol +++ b/mainnet-contracts/src/PufLocker.sol @@ -5,7 +5,6 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { AccessManagedUpgradeable } from "@openzeppelin-contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol"; -import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { PufLockerStorage } from "./PufLockerStorage.sol"; import { IPufLocker } from "./interface/IPufLocker.sol"; import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; diff --git a/mainnet-contracts/src/PufToken.sol b/mainnet-contracts/src/PufToken.sol index 25679a6..aa263f0 100644 --- a/mainnet-contracts/src/PufToken.sol +++ b/mainnet-contracts/src/PufToken.sol @@ -128,6 +128,7 @@ contract PufToken is IPufStakingPool, ERC20, ERC20Permit { /** * @inheritdoc IPufStakingPool */ + // solhint-disable-next-line gas-calldata-parameters function migrateWithSignature( address depositor, address migratorContract, diff --git a/mainnet-contracts/src/PufferDepositorV2.sol b/mainnet-contracts/src/PufferDepositorV2.sol index 7285080..f007f21 100644 --- a/mainnet-contracts/src/PufferDepositorV2.sol +++ b/mainnet-contracts/src/PufferDepositorV2.sol @@ -28,7 +28,7 @@ contract PufferDepositorV2 is IPufferDepositorV2, PufferDepositorStorage, Access /** * @dev Wallet that transferred pufETH to the PufferDepositor by mistake. */ - address private constant PUFFER = 0x8A0C1e5cEA8e0F6dF341C005335E7fe5ed18A0a0; + address private constant _PUFFER = 0x8A0C1e5cEA8e0F6dF341C005335E7fe5ed18A0a0; /** * @dev The Puffer Vault contract address @@ -46,7 +46,7 @@ contract PufferDepositorV2 is IPufferDepositorV2, PufferDepositorStorage, Access function initialize() public reinitializer(2) { // https://etherscan.io/token/0xd9a442856c234a39a81a089c06451ebaa4306a72?a=0x4aa799c5dfc01ee7d790e3bf1a7c2257ce1dceff // slither-disable-next-line unchecked-transfer - PUFFER_VAULT.transfer(PUFFER, 0.201 ether); + PUFFER_VAULT.transfer(_PUFFER, 0.201 ether); } /** diff --git a/mainnet-contracts/src/PufferL2Depositor.sol b/mainnet-contracts/src/PufferL2Depositor.sol index 73b51f0..33d8d31 100644 --- a/mainnet-contracts/src/PufferL2Depositor.sol +++ b/mainnet-contracts/src/PufferL2Depositor.sol @@ -163,7 +163,13 @@ contract PufferL2Depositor is IPufferL2Depositor, AccessManaged { pufToken.deposit(depositor, account, amount); } - emit DepositedToken(token, msg.sender, account, amount, referralCode); + emit DepositedToken({ + token: token, + depositor: msg.sender, + account: account, + tokenAmount: amount, + referralCode: referralCode + }); } function _addNewToken(address token) internal { diff --git a/mainnet-contracts/src/PufferModule.sol b/mainnet-contracts/src/PufferModule.sol index aba2ddb..c3fba94 100644 --- a/mainnet-contracts/src/PufferModule.sol +++ b/mainnet-contracts/src/PufferModule.sol @@ -260,6 +260,7 @@ contract PufferModule is IPufferModule, Initializable, AccessManagedUpgradeable /** * @inheritdoc IPufferModule */ + // solhint-disable-next-line func-name-mixedcase function NAME() external view returns (bytes32) { ModuleStorage storage $ = _getPufferModuleStorage(); return $.moduleName; diff --git a/mainnet-contracts/src/PufferModuleManager.sol b/mainnet-contracts/src/PufferModuleManager.sol index bbd39a2..bddaae7 100644 --- a/mainnet-contracts/src/PufferModuleManager.sol +++ b/mainnet-contracts/src/PufferModuleManager.sol @@ -104,8 +104,8 @@ contract PufferModuleManager is IPufferModuleManager, AccessManagedUpgradeable, uint256 sharesWithdrawn; - for (uint256 i = 0; i < withdrawals.length; i++) { - for (uint256 j = 0; j < withdrawals[i].shares.length; j++) { + for (uint256 i = 0; i < withdrawals.length; ++i) { + for (uint256 j = 0; j < withdrawals[i].shares.length; ++j) { sharesWithdrawn += withdrawals[i].shares[j]; } } @@ -146,7 +146,8 @@ contract PufferModuleManager is IPufferModuleManager, AccessManagedUpgradeable, { uint256 totalRewardsAmount; - for (uint256 i = 0; i < modules.length; i++) { + for (uint256 i = 0; i < modules.length; ++i) { + //solhint-disable-next-line avoid-low-level-calls (bool success,) = IPufferModule(modules[i]).call(address(this), rewardsAmounts[i], ""); if (!success) { revert InvalidAmount(); @@ -362,7 +363,7 @@ contract PufferModuleManager is IPufferModuleManager, AccessManagedUpgradeable, * @dev Restricted to the DAO */ function callStartCheckpoint(address[] calldata moduleAddresses) external virtual restricted { - for (uint256 i = 0; i < moduleAddresses.length; i++) { + for (uint256 i = 0; i < moduleAddresses.length; ++i) { // reverts if supplied with a duplicate module address IPufferModule(moduleAddresses[i]).startCheckpoint(); } @@ -389,7 +390,7 @@ contract PufferModuleManager is IPufferModuleManager, AccessManagedUpgradeable, function callUpdateOperatorAVSSocket( IRestakingOperator restakingOperator, address avsRegistryCoordinator, - string memory socket + string calldata socket ) external virtual restricted { restakingOperator.updateOperatorAVSSocket(avsRegistryCoordinator, socket); diff --git a/mainnet-contracts/src/PufferOracleV2.sol b/mainnet-contracts/src/PufferOracleV2.sol index 8c59d5c..fe77edb 100644 --- a/mainnet-contracts/src/PufferOracleV2.sol +++ b/mainnet-contracts/src/PufferOracleV2.sol @@ -3,6 +3,7 @@ pragma solidity >=0.8.0 <0.9.0; import { IGuardianModule } from "./interface/IGuardianModule.sol"; import { IPufferOracleV2 } from "./interface/IPufferOracleV2.sol"; +//solhint-disable-next-line no-unused-import import { IPufferOracle } from "./interface/IPufferOracle.sol"; import { AccessManaged } from "@openzeppelin/contracts/access/manager/AccessManaged.sol"; diff --git a/mainnet-contracts/src/PufferProtocol.sol b/mainnet-contracts/src/PufferProtocol.sol index 334f0a5..5b3a7eb 100644 --- a/mainnet-contracts/src/PufferProtocol.sol +++ b/mainnet-contracts/src/PufferProtocol.sol @@ -364,6 +364,7 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad // The excess is the rewards amount for that Node Operator uint256 transferAmount = validatorInfos[i].withdrawalAmount > 32 ether ? 32 ether : validatorInfos[i].withdrawalAmount; + //solhint-disable-next-line avoid-low-level-calls (bool success,) = IPufferModule(validatorInfos[i].module).call(address(PUFFER_VAULT), transferAmount, ""); if (!success) { revert Failed(); @@ -843,7 +844,7 @@ contract PufferProtocol is IPufferProtocol, AccessManagedUpgradeable, UUPSUpgrad } function _decreaseNumberOfRegisteredValidators(ProtocolStorage storage $, bytes32 moduleName) internal { - $.moduleLimits[moduleName].numberOfRegisteredValidators -= 1; + --$.moduleLimits[moduleName].numberOfRegisteredValidators; emit NumberOfRegisteredValidatorsChanged(moduleName, $.moduleLimits[moduleName].numberOfRegisteredValidators); } diff --git a/mainnet-contracts/src/PufferVaultV2.sol b/mainnet-contracts/src/PufferVaultV2.sol index d917a79..d0adf04 100644 --- a/mainnet-contracts/src/PufferVaultV2.sol +++ b/mainnet-contracts/src/PufferVaultV2.sol @@ -47,8 +47,8 @@ contract PufferVaultV2 is PufferVault, IPufferVaultV2 { /** * @dev Two wallets that transferred pufETH to the PufferVault by mistake. */ - address private constant WHALE_PUFFER = 0xe6957D9b493b2f2634c8898AC09dc14Cb24BE222; - address private constant PUFFER = 0x34c912C13De7953530DBE4c32F597d1bAF77889b; + address private constant _WHALE_PUFFER = 0xe6957D9b493b2f2634c8898AC09dc14Cb24BE222; + address private constant _PUFFER = 0x34c912C13De7953530DBE4c32F597d1bAF77889b; constructor( IStETH stETH, @@ -91,10 +91,10 @@ contract PufferVaultV2 is PufferVault, IPufferVaultV2 { // https://etherscan.io/tx/0x2e02a00dbc8ba48cd65a6802d174c210d0c4869806a564cca0088e42d382b2ff // slither-disable-next-line unchecked-transfer - this.transfer(WHALE_PUFFER, 299.864287100672938618 ether); + this.transfer(_WHALE_PUFFER, 299.864287100672938618 ether); // https://etherscan.io/tx/0x7d309dc26cb3f0226e480e0d4c598707faee59d58bfc68bedb75cf5055ac274a // slither-disable-next-line unchecked-transfer - this.transfer(PUFFER, 25426113577506618); + this.transfer(_PUFFER, 25426113577506618); } } @@ -600,6 +600,7 @@ contract PufferVaultV2 is PufferVault, IPufferVaultV2 { } modifier markDeposit() virtual { + //solhint-disable-next-line no-inline-assembly assembly { tstore(_DEPOSIT_TRACKER_LOCATION, 1) // Store `1` in the deposit tracker location } @@ -607,6 +608,7 @@ contract PufferVaultV2 is PufferVault, IPufferVaultV2 { } modifier revertIfDeposited() virtual { + //solhint-disable-next-line no-inline-assembly assembly { // If the deposit tracker location is set to `1`, revert with `DepositAndWithdrawalForbidden()` if tload(_DEPOSIT_TRACKER_LOCATION) { diff --git a/mainnet-contracts/src/PufferVaultV3.sol b/mainnet-contracts/src/PufferVaultV3.sol index f776e34..3b18dc3 100644 --- a/mainnet-contracts/src/PufferVaultV3.sol +++ b/mainnet-contracts/src/PufferVaultV3.sol @@ -10,8 +10,6 @@ import { IDelegationManager } from "./interface/EigenLayer/IDelegationManager.so import { IWETH } from "./interface/Other/IWETH.sol"; import { IPufferVaultV3 } from "./interface/IPufferVaultV3.sol"; import { IPufferOracle } from "./interface/IPufferOracle.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; /** diff --git a/mainnet-contracts/src/PufferWithdrawalManager.sol b/mainnet-contracts/src/PufferWithdrawalManager.sol index 7561e7c..c1678cd 100644 --- a/mainnet-contracts/src/PufferWithdrawalManager.sol +++ b/mainnet-contracts/src/PufferWithdrawalManager.sol @@ -3,7 +3,6 @@ pragma solidity >=0.8.0 <0.9.0; import { PufferVaultV3 } from "./PufferVaultV3.sol"; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; -import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { IPufferWithdrawalManager } from "./interface/IPufferWithdrawalManager.sol"; import { PufferWithdrawalManagerStorage } from "./PufferWithdrawalManagerStorage.sol"; import { AccessManagedUpgradeable } from @@ -71,6 +70,7 @@ contract PufferWithdrawalManager is * @notice Only one withdrawal request per transaction is allowed */ modifier oneWithdrawalRequestAllowed() virtual { + // solhint-disable-next-line no-inline-assembly assembly { // If the deposit tracker location is set to `1`, revert with `MultipleWithdrawalsAreForbidden()` if tload(_WITHDRAWAL_REQUEST_TRACKER_LOCATION) { @@ -78,6 +78,7 @@ contract PufferWithdrawalManager is revert(0x1c, 0x04) // Revert by returning those 4 bytes. `revert MultipleWithdrawalsAreForbidden()` } } + // solhint-disable-next-line no-inline-assembly assembly { tstore(_WITHDRAWAL_REQUEST_TRACKER_LOCATION, 1) // Store `1` in the deposit tracker location } diff --git a/mainnet-contracts/src/RestakingOperator.sol b/mainnet-contracts/src/RestakingOperator.sol index 1380b32..734881c 100644 --- a/mainnet-contracts/src/RestakingOperator.sol +++ b/mainnet-contracts/src/RestakingOperator.sol @@ -154,7 +154,7 @@ contract RestakingOperator is IRestakingOperator, IERC1271, Initializable, Acces bytes calldata quorumNumbers, string calldata socket, IBLSApkRegistry.PubkeyRegistrationParams calldata params, - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ISignatureUtils.SignatureWithSaltAndExpiry calldata operatorSignature ) external virtual onlyPufferModuleManager { IRegistryCoordinatorExtended(avsRegistryCoordinator).registerOperator({ quorumNumbers: quorumNumbers, @@ -174,8 +174,8 @@ contract RestakingOperator is IRestakingOperator, IERC1271, Initializable, Acces string calldata socket, IBLSApkRegistry.PubkeyRegistrationParams calldata params, IRegistryCoordinator.OperatorKickParam[] calldata operatorKickParams, - ISignatureUtils.SignatureWithSaltAndExpiry memory churnApproverSignature, - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ISignatureUtils.SignatureWithSaltAndExpiry calldata churnApproverSignature, + ISignatureUtils.SignatureWithSaltAndExpiry calldata operatorSignature ) external virtual onlyPufferModuleManager { IRegistryCoordinatorExtended(avsRegistryCoordinator).registerOperatorWithChurn({ quorumNumbers: quorumNumbers, @@ -216,7 +216,7 @@ contract RestakingOperator is IRestakingOperator, IERC1271, Initializable, Acces * @inheritdoc IRestakingOperator * @dev Restricted to the PufferModuleManager */ - function updateOperatorAVSSocket(address avsRegistryCoordinator, string memory socket) + function updateOperatorAVSSocket(address avsRegistryCoordinator, string calldata socket) external virtual onlyPufferModuleManager diff --git a/semgrep-rules/best-practice/use-abi-encodecall-instead-of-encodewithselector.sol b/semgrep-rules/best-practice/use-abi-encodecall-instead-of-encodewithselector.sol new file mode 100644 index 0000000..ddeb25c --- /dev/null +++ b/semgrep-rules/best-practice/use-abi-encodecall-instead-of-encodewithselector.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract TestCall { + function make_call(address collection, uint256 a, uint256 b) external returns(bool success, bytes memory data) { + // ok: use-abi-encodecall-instead-of-encodewithselector + (success, data) = collection.staticcall(abi.encodeCall(Test.divide, (a, b))); + } + + function make_call2(address collection, uint256 a, uint256 b) external returns(bool success, bytes memory data) { + // ruleid: use-abi-encodecall-instead-of-encodewithselector + (success, data) = collection.call(abi.encodeWithSelector(ITest.divide.selector, (a, b))); + } + + function _transferNFT( + address collection, + uint256 assetType, + address sender, + address recipient, + uint256[] memory itemIds, + uint256[] memory amounts + ) internal { + address transferManager = managerSelectorOfAssetType[assetType].transferManager; + + if (transferManager == address(0)) { + revert NoTransferManagerForAssetType(assetType); + } + + // ruleid: use-abi-encodecall-instead-of-encodewithselector + (bool status, ) = transferManager.call( + abi.encodeWithSelector( + managerSelectorOfAssetType[assetType].selector, + collection, + sender, + recipient, + itemIds, + amounts + ) + ); + + if (!status) { + revert NFTTransferFail(collection, assetType); + } + } +} \ No newline at end of file diff --git a/semgrep-rules/best-practice/use-abi-encodecall-instead-of-encodewithselector.yaml b/semgrep-rules/best-practice/use-abi-encodecall-instead-of-encodewithselector.yaml new file mode 100644 index 0000000..1983532 --- /dev/null +++ b/semgrep-rules/best-practice/use-abi-encodecall-instead-of-encodewithselector.yaml @@ -0,0 +1,15 @@ +rules: + - id: use-abi-encodecall-instead-of-encodewithselector + message: To guarantee arguments type safety it is recommended to use `abi.encodeCall` instead of `abi.encodeWithSelector`. + metadata: + category: best-practice + references: + - https://blog.soliditylang.org/2021/12/20/solidity-0.8.11-release-announcement/ + technology: + - solidity + patterns: + - pattern: | + abi.encodeWithSelector(...); + languages: + - solidity + severity: INFO \ No newline at end of file diff --git a/semgrep-rules/best-practice/use-ownable2step.sol b/semgrep-rules/best-practice/use-ownable2step.sol new file mode 100644 index 0000000..b735e9e --- /dev/null +++ b/semgrep-rules/best-practice/use-ownable2step.sol @@ -0,0 +1,15 @@ +pragma solidity >=0.7.4; + +//ruleid: use-ownable2step +contract Test is ITest, Ownable { + function payment() public { + // ... + } +} + +//ok: use-ownable2step +contract Test is ITest, Ownable2Step { + function payment() public { + // ... + } +} \ No newline at end of file diff --git a/semgrep-rules/best-practice/use-ownable2step.yaml b/semgrep-rules/best-practice/use-ownable2step.yaml new file mode 100644 index 0000000..e4ff0f9 --- /dev/null +++ b/semgrep-rules/best-practice/use-ownable2step.yaml @@ -0,0 +1,25 @@ +rules: + - id: use-ownable2step + metadata: + category: best-practice + references: + - https://docs.openzeppelin.com/contracts/4.x/api/access#Ownable2Step + - https://www.rareskills.io/post/openzeppelin-ownable2step + technology: + - solidity + message: | + By demanding that the receiver of the owner permissions actively accept via a contract call of its own, + `Ownable2Step` and `Ownable2StepUpgradeable` prevent the contract ownership from accidentally being transferred + to an address that cannot handle it. + languages: + - solidity + severity: INFO + patterns: + - pattern-inside: | + contract $C is ...,$OWNABLE,... { + ... + } + - metavariable-regex: + metavariable: $OWNABLE + regex: (Ownable$|OwnableUpgradeable) + - focus-metavariable: $OWNABLE \ No newline at end of file diff --git a/semgrep-rules/performance/array-length-outside-loop.sol b/semgrep-rules/performance/array-length-outside-loop.sol new file mode 100644 index 0000000..03acdd4 --- /dev/null +++ b/semgrep-rules/performance/array-length-outside-loop.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Test { + + function doSmthng() public returns(uint256) { + // ruleid: array-length-outside-loop + for (uint256 i = 0; i < array.length;) { + // invariant: array's length is not changed + } + } +} + +contract Test2 { + address[2] all_data = [address(0), address(0)]; + address[] all_data_2; + function doSmthng() public returns(uint256) { + uint256 len = array.length; + // ok: array-length-outside-loop + for (uint256 i = 0; i < len;) { + // invariant: array's length is not changed + } + } + + function doSmthng2(address[] calldata arr) public returns(uint256) { + // ok: array-length-outside-loop + for (uint256 i = 0; i < arr.length;) { + // invariant: array's length is not changed + } + } + + function doSmthng3() public returns(uint256) { + address[2] storage arr = all_data; + // ok: array-length-outside-loop + for (uint256 i = 0; i < arr.length;) { + // invariant: array's length is not changed + } + } + + function doSmthng4() public returns(uint256) { + address[] storage arr = all_data_2; + // ruleid: array-length-outside-loop + for (uint256 i = 0; i < arr.length;) { + // invariant: array's length is not changed + } + } + +} + +contract Test3 { + + function doSmthng() public returns(uint256) { + uint256 i = 0; + while (i != 200) { + i++; + // ruleid: array-length-outside-loop + uint256 len = array.length; + // invariant: array's length is not changed + } + } +} \ No newline at end of file diff --git a/semgrep-rules/performance/array-length-outside-loop.yaml b/semgrep-rules/performance/array-length-outside-loop.yaml new file mode 100644 index 0000000..3684ba3 --- /dev/null +++ b/semgrep-rules/performance/array-length-outside-loop.yaml @@ -0,0 +1,41 @@ +rules: + - id: array-length-outside-loop + message: Caching the array length outside a loop saves reading it on each + iteration, as long as the array's length is not changed during the loop. + metadata: + references: + - https://github.com/byterocket/c4-common-issues/blob/main/0-Gas-Optimizations.md/#g002---cache-array-length-outside-of-loop + category: performance + technology: + - solidity + patterns: + - pattern-either: + - pattern-inside: | + for (...) { + ... + } + - pattern-inside: | + while (...) { + ... + } + - pattern-not-inside: | + function $F(..., $TYPE calldata $VAR, ...) { + ... + } + - pattern-not-inside: | + function $F(...) { + ... + $TYPE[...] storage $VAR; + ... + } + - pattern-not-inside: | + function $F(...) { + ... + $TYPE[...] storage $VAR = ...; + ... + } + - pattern: | + $VAR.length + languages: + - solidity + severity: INFO \ No newline at end of file diff --git a/semgrep-rules/performance/inefficient-state-variable-increment.sol b/semgrep-rules/performance/inefficient-state-variable-increment.sol new file mode 100644 index 0000000..5e99b61 --- /dev/null +++ b/semgrep-rules/performance/inefficient-state-variable-increment.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.2; + +contract A { + uint256[10] public a; + uint256 public b; + function one() public { + a[0] = 4; + b = 5; + // ruleid: inefficient-state-variable-increment + a[0] += b; + } +} + + +contract B { + uint256 public a; + uint256 public b; + function one() public { + a = 4; + b = 5; + // ok: inefficient-state-variable-increment + a = a + b; + } +} + +contract C { + uint256 public a = 4; + uint256 public b; + function one() public { + b = 5; + // ruleid: inefficient-state-variable-increment + a += b; + } +} + +contract D { + function one() public { + a = 4; + b = 5; + // ok: inefficient-state-variable-increment + a += b; + } +} + +contract E { + mapping (address => uint) public a; + function one() public { + // ok: inefficient-state-variable-increment + a[msg.sender] += 4; + } +} \ No newline at end of file diff --git a/semgrep-rules/performance/inefficient-state-variable-increment.yaml b/semgrep-rules/performance/inefficient-state-variable-increment.yaml new file mode 100644 index 0000000..04fa2c5 --- /dev/null +++ b/semgrep-rules/performance/inefficient-state-variable-increment.yaml @@ -0,0 +1,37 @@ +rules: + - + id: inefficient-state-variable-increment + message: | + += costs more gas than = + for state variables. + metadata: + references: + - https://gist.github.com/IllIllI000/cbbfb267425b898e5be734d4008d4fe8 + category: performance + technology: + - solidity + patterns: + - pattern-either: + - pattern: | + $X += $Y + - pattern: | + $X[...] += $Y + - pattern-either: + - pattern-inside: | + contract $C { + ... + $TYPE $X; + ... + } + - pattern-inside: | + contract $C { + ... + $TYPE $X = ...; + ... + } + - metavariable-regex: + metavariable: $TYPE + regex: uint + languages: + - solidity + severity: INFO + \ No newline at end of file diff --git a/semgrep-rules/performance/init-variables-with-default-value.sol b/semgrep-rules/performance/init-variables-with-default-value.sol new file mode 100644 index 0000000..275e0b7 --- /dev/null +++ b/semgrep-rules/performance/init-variables-with-default-value.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Example_1 { + // ok: init-variables-with-default-value + bool constant var5 = false; + function setUint() public returns(uint256) { + // ok: init-variables-with-default-value + uint256 x = 0; + return x; + } +} + +contract Example_2 { + // ruleid: init-variables-with-default-value + uint256 y = 0; +} + +contract Example_3 { + // ruleid: init-variables-with-default-value + bytes variable = ""; + function setBool() public returns(bool) { + // ok: init-variables-with-default-value + bool z = false; + return z; + } +} + + +contract Example_4 { + // ok: init-variables-with-default-value + uint256 immutable variable = 0; + constructor(){} + function setBool() public returns(bool) { + // ok: init-variables-with-default-value + bool z = false; + return z; + } +} + +contract Example_5 { + // ok: init-variables-with-default-value + uint256 immutable variable; + constructor(){ + variable = 0; + } + function setBool() public returns(bool) { + // ok: init-variables-with-default-value + bool z = false; + return z; + } +} \ No newline at end of file diff --git a/semgrep-rules/performance/init-variables-with-default-value.yaml b/semgrep-rules/performance/init-variables-with-default-value.yaml new file mode 100644 index 0000000..0992c47 --- /dev/null +++ b/semgrep-rules/performance/init-variables-with-default-value.yaml @@ -0,0 +1,31 @@ +rules: + - id: init-variables-with-default-value + message: | + Uninitialized variables are assigned with the types default value. + Explicitly initializing a variable with its default value costs unnecessary gas. + metadata: + references: + - https://github.com/byterocket/c4-common-issues/blob/main/0-Gas-Optimizations.md/#g001---dont-initialize-variables-with-default-value + category: performance + technology: + - solidity + patterns: + - pattern-either: + - pattern: $TYPE $VAR = 0 + - pattern: $TYPE $VAR = false + - pattern: $TYPE $VAR = "" + - pattern: $TYPE $VAR = '' + - pattern-not: $TYPE constant $VAR = ...; + - pattern-not-inside: | + contract $C { + ... + $TYPE immutable $VAR = ...; + ... + } + - pattern-not-inside: | + function $F(...) { + ... + } + languages: + - solidity + severity: INFO diff --git a/semgrep-rules/performance/non-optimal-variables-swap.sol b/semgrep-rules/performance/non-optimal-variables-swap.sol new file mode 100644 index 0000000..2f967f4 --- /dev/null +++ b/semgrep-rules/performance/non-optimal-variables-swap.sol @@ -0,0 +1,46 @@ +pragma solidity 0.8.0; + + +contract Test1{ + uint256 a; + uint256 b; + uint256 c; + + function f1() { + // ruleid: non-optimal-variables-swap + c = a; + a = b; + b = c; + } +} + +contract Tes2{ + uint256 a; + uint256 b; + + function f1() { + // ruleid: non-optimal-variables-swap + uint256 c = a; + a = b; + b = c; + } + + function f1() { + // ruleid: non-optimal-variables-swap + uint256 c = a; + a = b; + f1(); + b = c; + } +} + +contract Test3{ + uint256 a; + uint256 b; + uint256 c; + + function f1() { + // ok: non-optimal-variables-swap + (b, a) = (a, b); + } +} \ No newline at end of file diff --git a/semgrep-rules/performance/non-optimal-variables-swap.yaml b/semgrep-rules/performance/non-optimal-variables-swap.yaml new file mode 100644 index 0000000..3354f66 --- /dev/null +++ b/semgrep-rules/performance/non-optimal-variables-swap.yaml @@ -0,0 +1,18 @@ +rules: + - id: non-optimal-variables-swap + message: Consider swapping variables using `($VAR1, $VAR2) = ($VAR2, $VAR1)` to save gas + languages: [solidity] + severity: INFO + metadata: + category: performance + technology: + - solidity + references: + - https://dev.to/oliverjumpertz/solidity-quick-tip-efficiently-swap-two-variables-1f8i + patterns: + - pattern: | + $TMP = $VAR1; + ... + $VAR1 = $VAR2; + ... + $VAR2 = $TMP; diff --git a/semgrep-rules/performance/state-variable-read-in-a-loop.sol b/semgrep-rules/performance/state-variable-read-in-a-loop.sol new file mode 100644 index 0000000..76154b7 --- /dev/null +++ b/semgrep-rules/performance/state-variable-read-in-a-loop.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Test { + uint256 z; + function doit1(uint256 a) public pure returns (uint256 x) { + // ruleid: state-variable-read-in-a-loop + for (uint256 i; i10, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); // "a"*33 + } +} \ No newline at end of file diff --git a/semgrep-rules/performance/use-custom-error-not-require.yaml b/semgrep-rules/performance/use-custom-error-not-require.yaml new file mode 100644 index 0000000..16d881d --- /dev/null +++ b/semgrep-rules/performance/use-custom-error-not-require.yaml @@ -0,0 +1,18 @@ +rules: + - + id: use-custom-error-not-require + message: | + Do not use require statements with a string message, use custom errors + metadata: + references: + - https://soliditylang.org/blog/2024/09/04/solidity-0.8.27-release-announcement + category: performance + technology: + - solidity + patterns: + - pattern-either: + - pattern-regex: require\(\s*[^,]+\s*,\s*"[^"]*"\s*\); + languages: + - solidity + severity: INFO + \ No newline at end of file diff --git a/semgrep-rules/performance/use-multiple-require.sol b/semgrep-rules/performance/use-multiple-require.sol new file mode 100644 index 0000000..6bc2d21 --- /dev/null +++ b/semgrep-rules/performance/use-multiple-require.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Test { + function doit1(uint256 a) public pure returns (uint256 x) { + // ok: use-multiple-require + require (1 == 1 || 2==2, "..."); + } + + function doit2(uint256 a) public pure returns (uint256 x) { + // ruleid: use-multiple-require + require(1==1 && 2==2, "smth went wrong"); + } + + function doit2(uint256 a) public pure returns (uint256 x) { + // ok: use-multiple-require + require(msg.sender == admin, "Timelock::cancelTransaction: Call must come from admin."); + } +} \ No newline at end of file diff --git a/semgrep-rules/performance/use-multiple-require.yaml b/semgrep-rules/performance/use-multiple-require.yaml new file mode 100644 index 0000000..bb89d7d --- /dev/null +++ b/semgrep-rules/performance/use-multiple-require.yaml @@ -0,0 +1,18 @@ +rules: + - id: use-multiple-require + message: | + Using multiple require statements is cheaper than using && multiple check combinations. + There are more advantages, such as easier to read code and better coverage reports. + metadata: + references: + - https://code4rena.com/reports/2023-01-ondo#g-15-splitting-require-statements-that-use--saves-gas---saves-8-gas-per- + category: performance + technology: + - solidity + patterns: + - pattern: | + require (<... $X && $Y ...>, ...); + languages: + - solidity + severity: INFO + \ No newline at end of file diff --git a/semgrep-rules/performance/use-prefix-decrement-not-postfix.sol b/semgrep-rules/performance/use-prefix-decrement-not-postfix.sol new file mode 100644 index 0000000..a69720e --- /dev/null +++ b/semgrep-rules/performance/use-prefix-decrement-not-postfix.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.2; + +contract TestRequiere { + function test(uint256 a) public { + // ruleid: use-prefix-decrement-not-postfix + for (uint i=len; i > 0; i--) { + if (i % 2 == 0) { + // ruleid: use-prefix-decrement-not-postfix + counter--; + } + // ok: use-prefix-decrement-not-postfix + uint256 k = 5 + i--; + } + } + + function test2() public { + // ok: use-prefix-decrement-not-postfix + for (uint i=len; i > 0; --i) { + if (i % 2 == 0) { + // ok: use-prefix-decrement-not-postfix + --counter; + } + // ... + } + } + + function test3() public { + for (uint i; i < len;) { + // ok: use-prefix-decrement-not-postfix + if (i-- == 5) { + } + // ... + } + } + + function test4() public { + // ruleid: use-prefix-decrement-not-postfix + proposalCount--; + proposal = Proposal({ + // ok: use-prefix-decrement-not-postfix + id: proposalCount--, + targets: targets, + values: values, + signatures: signatures, + calldatas: calldatas, + eta: eta, + executed: false + }); + } + + function test5() public returns (uint) { + // ok: use-prefix-decrement-not-postfix + return (count--); + } +} + + diff --git a/semgrep-rules/performance/use-prefix-decrement-not-postfix.yaml b/semgrep-rules/performance/use-prefix-decrement-not-postfix.yaml new file mode 100644 index 0000000..f7673aa --- /dev/null +++ b/semgrep-rules/performance/use-prefix-decrement-not-postfix.yaml @@ -0,0 +1,30 @@ +rules: + - id: use-prefix-decrement-not-postfix + message: | + Consider using the prefix decrement expression whenever the return value is not needed. + The prefix decrement expression is cheaper in terms of gas. + metadata: + references: + - https://github.com/byterocket/c4-common-issues/blob/main/0-Gas-Optimizations.md/#g012---use-prefix-increment-instead-of-postfix-increment-if-possible + category: performance + technology: + - solidity + patterns: + - pattern: $VAR-- + - pattern-not-inside: | + $B = ... + - pattern-not-inside: | + if (<... $VAR-- ...>) { + ... + } + - pattern-not-inside: require (<... $VAR-- ...>) + - pattern-not-inside: | + while (<... $VAR-- ...>) { + ... + } + - pattern-not-inside: | + return ...; + languages: + - solidity + severity: INFO + \ No newline at end of file diff --git a/semgrep-rules/performance/use-prefix-increment-not-postfix.sol b/semgrep-rules/performance/use-prefix-increment-not-postfix.sol new file mode 100644 index 0000000..f9327df --- /dev/null +++ b/semgrep-rules/performance/use-prefix-increment-not-postfix.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.2; + +contract TestRequiere { + function test(uint256 a) public { + // ruleid: use-prefix-increment-not-postfix + for (uint i; i < len; i++) { + if (i % 2 == 0) { + // ruleid: use-prefix-increment-not-postfix + counter++; + } + // ok: use-prefix-increment-not-postfix + uint256 k = 5 + i++; + } + } + + function test2() public { + // ok: use-prefix-increment-not-postfix + for (uint i; i < len; ++i) { + if (i % 2 == 0) { + // ok: use-prefix-increment-not-postfix + ++counter; + } + // ... + } + } + + function test3() public { + for (uint i; i < len;) { + // ok: use-prefix-increment-not-postfix + if (i++ == 5) { + } + // ... + } + } + + function test4() public { + // ruleid: use-prefix-increment-not-postfix + proposalCount++; + proposal = Proposal({ + // ok: use-prefix-increment-not-postfix + id: proposalCount++, + targets: targets, + values: values, + signatures: signatures, + calldatas: calldatas, + eta: eta, + executed: false + }); + } + + function test5() public returns (uint) { + // ok: use-prefix-increment-not-postfix + return (count++); + } +} + + diff --git a/semgrep-rules/performance/use-prefix-increment-not-postfix.yaml b/semgrep-rules/performance/use-prefix-increment-not-postfix.yaml new file mode 100644 index 0000000..f278fe0 --- /dev/null +++ b/semgrep-rules/performance/use-prefix-increment-not-postfix.yaml @@ -0,0 +1,30 @@ +rules: + - id: use-prefix-increment-not-postfix + message: | + Consider using the prefix increment expression whenever the return value is not needed. + The prefix increment expression is cheaper in terms of gas. + metadata: + references: + - https://github.com/byterocket/c4-common-issues/blob/main/0-Gas-Optimizations.md/#g012---use-prefix-increment-instead-of-postfix-increment-if-possible + category: performance + technology: + - solidity + patterns: + - pattern: $VAR++ + - pattern-not-inside: | + $B = ... + - pattern-not-inside: | + if (<... $VAR++ ...>) { + ... + } + - pattern-not-inside: require (<... $VAR++ ...>) + - pattern-not-inside: | + while (<... $VAR++ ...>) { + ... + } + - pattern-not-inside: | + return ...; + languages: + - solidity + severity: INFO + \ No newline at end of file diff --git a/semgrep-rules/performance/use-short-revert-string.sol b/semgrep-rules/performance/use-short-revert-string.sol new file mode 100644 index 0000000..585e7d6 --- /dev/null +++ b/semgrep-rules/performance/use-short-revert-string.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.2; + +contract TestRequiere { + function test(uint256 a) public { + // ruleid: use-short-revert-string + require(a>10, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); // "a"*33 + } + + function testRevert(uint256 a) public { + if (a > 10) { + // ruleid: use-short-revert-string + revert("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); // "a"*33 + } + } + + function test2(uint256 a) public { + // ok: use-short-revert-string + require(a>10, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); // "a"*32 + } + + function test3(uint256 a) public { + // ok: use-short-revert-string + require(a>10, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); // "a"*32 + } +} \ No newline at end of file diff --git a/semgrep-rules/performance/use-short-revert-string.yaml b/semgrep-rules/performance/use-short-revert-string.yaml new file mode 100644 index 0000000..8b119e4 --- /dev/null +++ b/semgrep-rules/performance/use-short-revert-string.yaml @@ -0,0 +1,25 @@ +rules: + - + id: use-short-revert-string + message: | + Shortening revert strings to fit in 32 bytes will decrease gas costs for deployment and + gas costs when the revert condition has been met. + metadata: + references: + - https://github.com/byterocket/c4-common-issues/blob/main/0-Gas-Optimizations.md/#g007---long-revert-strings + category: performance + technology: + - solidity + patterns: + - pattern-either: + - pattern: | + require(..., "$MSG"); + - pattern: | + revert("$MSG"); + - metavariable-regex: + metavariable: $MSG + regex: .{33,} + languages: + - solidity + severity: INFO + \ No newline at end of file diff --git a/semgrep-rules/security/accessible-selfdestruct.sol b/semgrep-rules/security/accessible-selfdestruct.sol new file mode 100644 index 0000000..b2ecd60 --- /dev/null +++ b/semgrep-rules/security/accessible-selfdestruct.sol @@ -0,0 +1,146 @@ +pragma solidity 0.8.19; + +contract Test{ + address owner; + + constructor(){ + owner = msg.sender; + } + + function func1(address to) external{ + //ruleid: accessible-selfdestruct + selfdestruct(to); + } + + function func2(address tmp, address to) public{ + //ruleid: accessible-selfdestruct + selfdestruct(to); + } + + function func3(address tmp, address tmp1, address to) public{ + //ruleid: accessible-selfdestruct + selfdestruct(to); + } + + function func4(address tmp, address tmp1, address tmp3, address to) external{ + //ruleid: accessible-selfdestruct + selfdestruct(to); + } + + function func5(address to) public{ + address payable addr = payable(to); + //ruleid: accessible-selfdestruct + selfdestruct(addr); + } + + function func6(address to) public onlyOwner { + address payable addr = payable(to); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func7(address to) external onlyOwner { + address payable addr = payable(to); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func8(address to) external checkAddress(to){ + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func9(address to) external{ + require(msg.sender == owner); + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func10(address to) external{ + require(msg.sender == owner, "Not an owner"); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func11(address to) external{ + require(_msgSender() == owner); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func12(address to) public{ + if (msg.sender == owner){ + //ok: accessible-selfdestruct + selfdestruct(to); + } + } + + function func13(address to) external{ + onlyOwner(msg.sender); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func14(address to) external{ + onlyOwner(msg.sender); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func15(address to) external{ + requireOwner(msg.sender); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func16(address to) external{ + _requireOwnership(msg.sender); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func17(address to) external{ + _requireOwnership(msg.sender); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func18(address to) external{ + Test1._enforceIsContractOwner(_msgSender); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func19(address to) external{ + Test1._enforceOwner(msg.sender); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func20(address to) external{ + Test1.enforceIsContractOwner(_msgSender); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func21(address to) external { + func22(to); + } + + function func22(address to) internal { + // ruleid: accessible-selfdestruct + selfdestruct(to); + } +} \ No newline at end of file diff --git a/semgrep-rules/security/accessible-selfdestruct.yaml b/semgrep-rules/security/accessible-selfdestruct.yaml new file mode 100644 index 0000000..a7ed3ee --- /dev/null +++ b/semgrep-rules/security/accessible-selfdestruct.yaml @@ -0,0 +1,108 @@ +rules: + - id: accessible-selfdestruct + severity: ERROR + languages: + - solidity + message: Contract can be destructed by anyone in $FUNC + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: LOW + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://www.parity.io/blog/a-postmortem-on-the-parity-multi-sig-library-self-destruct/ + mode: taint + pattern-sources: + - patterns: + - focus-metavariable: + - $ADDR + - pattern-either: + - pattern: function $FUNC(..., address $ADDR, ...) external { ... } + - pattern: function $FUNC(..., address $ADDR, ...) public { ... } + - pattern-not: function $FUNC(...) $MODIFIER { ... } + - pattern-not: function $FUNC(...) $MODIFIER(...) { ... } + - pattern-not: | + function $FUNC(...) { + ... + require(<... msg.sender ...>, ...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + assert(<... msg.sender ...>, ...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + require(<... _msgSender ...>, ...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + assert(<... _msgSender ...>, ...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + if (<... msg.sender ...>) { + ... + } + ... + } + - pattern-not: | + function $FUNC(...) { + ... + if (<... _msgSender ...>) { + ... + } + ... + } + - pattern-not: | + function $FUNC(...) { + ... + onlyOwner(...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + requireOwner(...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + _requireOwnership(...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + $C._enforceIsContractOwner(...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + $C._enforceOwner(...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + $C.enforceIsContractOwner(...); + ... + } + pattern-sinks: + - pattern-either: + - pattern: selfdestruct(...); + - pattern: suicide(...); \ No newline at end of file diff --git a/semgrep-rules/security/arbitrary-low-level-call.sol b/semgrep-rules/security/arbitrary-low-level-call.sol new file mode 100644 index 0000000..be1fe66 --- /dev/null +++ b/semgrep-rules/security/arbitrary-low-level-call.sol @@ -0,0 +1,887 @@ +// Sources flattened with hardhat v2.7.0 https://hardhat.org + +// File lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol + +// OpenZeppelin Contracts v4.4.1 (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 `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 lib/openzeppelin-contracts/contracts/utils/Address.sol + +// OpenZeppelin Contracts v4.4.1 (utils/Address.sol) + +pragma solidity ^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 + * ==== + * + * [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 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); + } + + /** + * @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 + + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + + +// File lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol + +// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol) + +pragma solidity ^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 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 lib/openzeppelin-contracts/contracts/utils/Context.sol + +// 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 lib/openzeppelin-contracts/contracts/access/Ownable.sol +// SPDX-License-Identifier: MIT + +// OpenZeppelin Contracts v4.4.1 (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 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); + } +} + + +// File src/veSTARS/Distributor.sol + +pragma solidity 0.8.11; + + + +interface ILocker { + function notifyRewardAmount(IERC20 rewardsToken, uint256 reward) external; +} + +interface ITreasury { + function withdrawTokens(address _token, address _to, uint256 _amount) external; + function withdrawFTM(address payable _to, uint256 _amount) external; + function transferOwnership(address newOwner) external; + +} + + +contract DistributorTreasury is Ownable { + using SafeERC20 for IERC20; + + address public metis = address(0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000); + address public stars = address(0xb26F58f0b301a077cFA779c0B0f8281C7f936Ac0); + + uint256 public weeklyAmountOfToken = 1000000 ether; + + address public treasury; + address public delegate; + address public multisig; + + uint256 public reward = 0.25 ether; + + uint256 public constant MAX_BPS = 10_000; + uint256 public constant MIN_FEE_BPS = 0; + uint256 public constant MAX_FEE_BPS = MAX_BPS / 2; + uint256 public feeBps = (MAX_BPS * 15) / 100; // 15% + + uint256 public nextNotify = 0; + + constructor(address _treasury, address _delegate, address _multisig) { + require (_treasury != address(0)); + require (_delegate != address(0)); + require (_multisig != address(0)); + treasury = _treasury; + delegate = _delegate; + multisig = _multisig; + } + + function weeklyNotify() external { + require(block.timestamp >= nextNotify, "not available"); + nextNotify = block.timestamp + 7 days; + + IERC20 coin = IERC20(metis); + ITreasury(treasury).withdrawTokens(address(coin), address(this), coin.balanceOf(address(treasury))); + coin.transfer(msg.sender, reward); + uint256 balance = coin.balanceOf(address(this)); + uint256 feeAmount = balance * feeBps / MAX_BPS; + coin.transfer(multisig, feeAmount); + balance = balance - feeAmount; + + coin.safeApprove(delegate, balance); + ILocker(delegate).notifyRewardAmount(coin, balance); + + IERC20 starsToken = IERC20(stars); + ITreasury(treasury).withdrawTokens(address(starsToken), address(this), weeklyAmountOfToken); + uint256 starsBalance = starsToken.balanceOf(address(this)); + starsToken.safeApprove(delegate, starsBalance); + ILocker(delegate).notifyRewardAmount(starsToken, starsBalance); + } + + // Admin functions + function manualNotify() external onlyOwner { + IERC20 coin = IERC20(metis); + uint256 balance = coin.balanceOf(address(this)); + coin.safeApprove(delegate, balance); + ILocker(delegate).notifyRewardAmount(coin, balance); + + IERC20 starsToken = IERC20(stars); + uint256 starsBalance = starsToken.balanceOf(address(this)); + starsToken.safeApprove(delegate, starsBalance); + ILocker(delegate).notifyRewardAmount(starsToken, starsBalance); + } + + function transferOwnershipOfTreasury(address _owner) external onlyOwner { + ITreasury(treasury).transferOwnership(_owner); + } + + function updateFeeBps(uint256 _newFeeBps) external onlyOwner { + require(_newFeeBps >= MIN_FEE_BPS && _newFeeBps <= MAX_FEE_BPS, "INVLD_FEE"); + feeBps = _newFeeBps; + } + + function withdrawTokens(address _token, address _to, uint256 _amount) external onlyOwner { + ITreasury(treasury).withdrawTokens(_token, _to, _amount); + } + + function setReward(uint256 _reward) external onlyOwner { + reward = _reward; + } + + function setWeeklyAmount(uint256 _weekly) external onlyOwner { + weeklyAmountOfToken = _weekly; + } + + function setTreasury(address _treasury) external onlyOwner { + treasury = _treasury; + } + + function setDelegate(address _delegate) external onlyOwner { + delegate = _delegate; + } + + function setMultisig(address _delegate) external onlyOwner { + delegate = _delegate; + } + + function resetNotify() external onlyOwner { + nextNotify = 0; + } + + function execute( + address to, + uint256 value, + bytes calldata data + ) external returns (bool, bytes memory) { + // ruleid: arbitrary-low-level-call + (bool success, bytes memory result) = to.call{value: value}(data); + + // ruleid: arbitrary-low-level-call + (bool success, bytes memory result) = to.call{gas: value}(data); + + // ruleid: arbitrary-low-level-call + (bool success, bytes memory result) = to.call(data); + + // ruleid: arbitrary-low-level-call + (bool success, bytes memory result) = to.call{value: value, gas: 0}(data); + + return (success, result); + } + + + function execute2( + address to, + uint256 value, + bytes calldata data + ) external { + execute3(to, value, data) + } + + function execute3( + address to, + uint256 value, + bytes calldata data + ) internal { + // ruleid: arbitrary-low-level-call + (bool success, bytes memory result) = to.call(data); + } + + receive() external payable {} +} + +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { ILiFi } from "../Interfaces/ILiFi.sol"; +import { LibSwap } from "./LibSwap.sol"; +import { LibAsset } from "../Libraries/LibAsset.sol"; +import { ReentrancyGuard } from "../Helpers/ReentrancyGuard.sol"; +import { SwapperV2 } from "../Helpers/SwapperV2.sol"; +import { Validatable } from "../Helpers/Validatable.sol"; +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +import { ERC20 } from "solady/tokens/ERC20.sol"; +import { NativeAssetTransferFailed, InvalidCallData } from "lifi/Errors/GenericErrors.sol"; + +interface IGasZip { + function deposit( + uint256 destinationChains, + address recipient + ) external payable; +} + +/// @title GasZipFacet +/// @author LI.FI (https://li.fi) +/// @notice Provides functionality to swap ERC20 tokens to native and deposit them to the gas.zip protocol (https://www.gas.zip/) +/// @custom:version 1.0.0 +contract GasZipFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { + using SafeTransferLib for address; + + /// @dev GasZip-specific bridge data + /// @param gasZipChainId The Gas.zip-specific chainId of the chain on which gas should be received on (https://dev.gas.zip/gas/chain-support/outbound) + struct GasZipData { + uint256 gasZipChainId; + } + + /// State /// + IGasZip public immutable gasZipRouter; + + /// Constructor /// + constructor(address _gasZipRouter) { + gasZipRouter = IGasZip(_gasZipRouter); + } + + function swap(bytes32 transactionId, SwapData calldata _swap) internal { + if (!LibAsset.isContract(_swap.callTo)) revert InvalidContract(); + uint256 fromAmount = _swap.fromAmount; + if (fromAmount == 0) revert NoSwapFromZeroBalance(); + uint256 nativeValue = LibAsset.isNativeAsset(_swap.sendingAssetId) + ? _swap.fromAmount + : 0; + uint256 initialSendingAssetBalance = LibAsset.getOwnBalance( + _swap.sendingAssetId + ); + uint256 initialReceivingAssetBalance = LibAsset.getOwnBalance( + _swap.receivingAssetId + ); + + if (nativeValue == 0) { + LibAsset.maxApproveERC20( + IERC20(_swap.sendingAssetId), + _swap.approveTo, + _swap.fromAmount + ); + } + + if (initialSendingAssetBalance < _swap.fromAmount) { + revert InsufficientBalance( + _swap.fromAmount, + initialSendingAssetBalance + ); + } + + // solhint-disable-next-line avoid-low-level-calls + // ruleid: arbitrary-low-level-call + (bool success, bytes memory res) = _swap.callTo.call{ + value: nativeValue + }(_swap.callData); + if (!success) { + LibUtil.revertWith(res); + } + + uint256 newBalance = LibAsset.getOwnBalance(_swap.receivingAssetId); + + emit AssetSwapped( + transactionId, + _swap.callTo, + _swap.sendingAssetId, + _swap.receivingAssetId, + _swap.fromAmount, + newBalance > initialReceivingAssetBalance + ? newBalance - initialReceivingAssetBalance + : newBalance, + block.timestamp + ); + } + + /// @notice Bridges tokens using the gas.zip protocol + /// @dev this function only supports native flow. For ERC20 flows this facet should be used as a protocol step instead + /// @param _bridgeData The core information needed for bridging + /// @param _gasZipData GasZip-specific bridge data + function startBridgeTokensViaGasZip( + ILiFi.BridgeData memory _bridgeData, + GasZipData calldata _gasZipData + ) + external + payable + nonReentrant + refundExcessNative(payable(msg.sender)) + validateBridgeData(_bridgeData) + doesNotContainSourceSwaps(_bridgeData) + doesNotContainDestinationCalls(_bridgeData) + { + // this function shall only be used for native assets + if (!LibAsset.isNativeAsset(_bridgeData.sendingAssetId)) + revert InvalidCallData(); + + depositToGasZipNative( + _bridgeData.minAmount, + _gasZipData.gasZipChainId, + _bridgeData.receiver + ); + + emit LiFiTransferStarted(_bridgeData); + } + + /// @notice Performs a swap before bridging via the gas.zip protocol + /// @param _bridgeData The core information needed for bridging + /// @param _swapData An array of swap related data for performing swaps before bridging + /// @param _gasZipData GasZip-specific bridge data + function swapAndStartBridgeTokensViaGasZip( + ILiFi.BridgeData memory _bridgeData, + LibSwap.SwapData[] calldata _swapData, + GasZipData calldata _gasZipData + ) + external + payable + nonReentrant + refundExcessNative(payable(msg.sender)) + containsSourceSwaps(_bridgeData) + doesNotContainDestinationCalls(_bridgeData) + validateBridgeData(_bridgeData) + { + // this function shall only be used for ERC20 assets + if (LibAsset.isNativeAsset(_bridgeData.sendingAssetId)) + revert InvalidCallData(); + + // deposit and swap ERC20 tokens + _bridgeData.minAmount = _depositAndSwap( + _bridgeData.transactionId, + _bridgeData.minAmount, + _swapData, + payable(msg.sender) + ); + + // deposit to gas.zip + depositToGasZipNative( + _bridgeData.minAmount, + _gasZipData.gasZipChainId, + _bridgeData.receiver + ); + + emit LiFiTransferStarted(_bridgeData); + } + + /// @notice Swaps ERC20 tokens to native and deposits these native tokens in the GasZip router contract + /// @dev this function can be used as a LibSwap.SwapData protocol step to combine it with any other bridge + /// @param _swapData The swap data that executes the swap from ERC20 to native + /// @param _destinationChains A value that represents a list of chains to which gas should be distributed (see https://dev.gas.zip/gas/code-examples/deposit for more details) + /// @param _recipient The address to receive the gas on dst chain + function depositToGasZipERC20( + LibSwap.SwapData calldata _swapData, + uint256 _destinationChains, + address _recipient + ) public { + // get the current native balance + uint256 currentNativeBalance = address(this).balance; + + // execute the swapData that swaps the ERC20 token into native + // semgrep currently does not propagate tainted values into libraries + // original line was: LibSwap.swap(0, _swapData); + swap(0, _swapData); + + // calculate the swap output amount using the initial native balance + uint256 swapOutputAmount = address(this).balance - + currentNativeBalance; + + // call the gas zip router and deposit tokens + gasZipRouter.deposit{ value: swapOutputAmount }( + _destinationChains, + _recipient + ); + } + + /// @notice Deposits native tokens in the GasZip router contract + /// @dev this function can be used as a LibSwap.SwapData protocol step to combine it with any other bridge + /// @param _amountToZip The amount to be deposited to the protocol + /// @param _destinationChains a value that represents a list of chains to which gas should be distributed (see https://dev.gas.zip/gas/code-examples/deposit for more details) + /// @param _recipient the address to receive the gas on dst chain + function depositToGasZipNative( + uint256 _amountToZip, + uint256 _destinationChains, + address _recipient + ) public payable { + // call the gas zip router and deposit tokens + gasZipRouter.deposit{ value: _amountToZip }( + _destinationChains, + _recipient + ); + } +} + diff --git a/semgrep-rules/security/arbitrary-low-level-call.yaml b/semgrep-rules/security/arbitrary-low-level-call.yaml new file mode 100644 index 0000000..bea86e9 --- /dev/null +++ b/semgrep-rules/security/arbitrary-low-level-call.yaml @@ -0,0 +1,39 @@ +rules: + - + id: arbitrary-low-level-call + message: An attacker may perform call() to an arbitrary address with controlled calldata + metadata: + category: security + technology: + - solidity + cwe: "CWE-20: Improper Input Validation" + confidence: LOW + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/CertiKAlert/status/1512198846343954445 + - https://twitter.com/SlowMist_Team/status/1508787862791069700 + - https://twitter.com/Beosin_com/status/1509099103401127942 + - https://blocksecteam.medium.com/li-fi-attack-a-cross-chain-bridge-vulnerability-no-its-due-to-unchecked-external-call-c31e7dadf60f + - https://etherscan.io/address/0xe7597f774fd0a15a617894dc39d45a28b97afa4f # Auctus Options + - https://etherscan.io/address/0x73a499e043b03fc047189ab1ba72eb595ff1fc8e # Li.Fi + - https://x.com/DecurityHQ/status/1813195945477087718 # Li.Fi hack #2 + mode: taint + pattern-sources: + - pattern: function $F(..., address $ADDR, ..., bytes calldata $DATA, ...) external { ... } + - pattern: function $F(..., address $ADDR, ..., bytes calldata $DATA, ...) public { ... } + - pattern: function $F(..., $TYPE calldata $DATA, ...) public { ... } + - pattern: function $F(..., $TYPE calldata $DATA, ...) external { ... } + pattern-sinks: + - pattern: $ADDR.call($DATA); + - pattern: $ADDR.call{$VALUE:...}($DATA); + - pattern: $ADDR.call{$VALUE:..., $GAS:...}($DATA); + - pattern: $DATA.$ADDR.call($DATA.$CALLDATA); + - pattern: $DATA.$ADDR.call{$VALUE:...}($DATA.$CALLDATA); + - pattern: $DATA.$ADDR.call{$VALUE:..., $GAS:...}($DATA.$CALLDATA); + languages: + - solidity + severity: ERROR + diff --git a/semgrep-rules/security/bad-transferfrom-access-control.sol b/semgrep-rules/security/bad-transferfrom-access-control.sol new file mode 100644 index 0000000..569a439 --- /dev/null +++ b/semgrep-rules/security/bad-transferfrom-access-control.sol @@ -0,0 +1,242 @@ +contract Test { + function func1(address from, address to) public { + // ruleid: bad-transferfrom-access-control + usdc.transferFrom(from, to, amount); + } + + function func2(address from, address to) public { + // ok: bad-transferfrom-access-control + usdc.transferFrom(owner, random, amount); + } + + function func3(address from, address to) public { + // ok: bad-transferfrom-access-control + usdc.transferFrom(pool, to, amount); + } + + function func4(address from, uint256 amount, address random) public { + // ok: bad-transferfrom-access-control + usdc.transferFrom(pool, owner, amount); + } + + function func5(address from, address to) external { + // ruleid: bad-transferfrom-access-control + usdc.transferFrom(from, to, amount); + } + + function func6(address from, address to) external { + // ok: bad-transferfrom-access-control + usdc.transferFrom(owner, random, amount); + } + + function func7(address from, address to) external { + // ok: bad-transferfrom-access-control + usdc.transferFrom(pool, to, amount); + } + + function func8(address from, uint256 amount, address random) external { + // ok: bad-transferfrom-access-control + usdc.transferFrom(pool, owner, amount); + } + + function transferFee(uint256 amount, uint256 feeBps, address token, address from, address to) + public + returns (uint256) + { + uint256 fee = calculateFee(amount, feeBps); + if (fee > 0) { + if (token != NATIVE_TOKEN) { + // ERC20 token + if (from == address(this)) { + TransferHelper.safeTransfer(token, to, fee); + } else { + // safeTransferFrom requires approval + // ruleid: bad-transferfrom-access-control + TransferHelper.transferFrom(token, from, to, fee); + } + } else { + require(from == address(this), "can only transfer eth from the router address"); + + // Native ether + (bool success,) = to.call{value: fee}(""); + require(success, "transfer failed in transferFee"); + } + return fee; + } else { + return 0; + } + } + + function func9(address from, address to) external { + _func10(from, to, amount); + } + + function _func10(address from, address to) internal { + // todoruleid: bad-transferfrom-access-control + usdc.transferFrom(from, to, amount); + } + + + // SAFE TRANSFER TESTS + + function func11(address from, address to) public { + // ruleid: bad-transferfrom-access-control + usdc.safeTransferFrom(from, to, amount); + } + + function func12(address from, address to) public { + // ok: bad-transferfrom-access-control + usdc.safeTransferFrom(owner, random, amount); + } + + function func13(address from, address to) public { + // ok: bad-transferfrom-access-control + usdc.safeTransferFrom(pool, to, amount); + } + + function func14(address from, uint256 amount, address random) public { + // ok: bad-transferfrom-access-control + usdc.safeTransferFrom(pool, owner, amount); + } + + function func15(address from, address to) external { + // ruleid: bad-transferfrom-access-control + usdc.safeTransferFrom(from, to, amount); + } + + function func16(address from, address to) external { + // ok: bad-transferfrom-access-control + usdc.safeTransferFrom(owner, random, amount); + } + + function func17(address from, address to) external { + // ok: bad-transferfrom-access-control + usdc.safeTransferFrom(pool, to, amount); + } + + function func18(address from, uint256 amount, address random) external { + // ok: bad-transferfrom-access-control + usdc.safeTransferFrom(pool, owner, amount); + } + + function transferFee2(uint256 amount, uint256 feeBps, address token, address from, address to) + public + returns (uint256) + { + uint256 fee = calculateFee(amount, feeBps); + if (fee > 0) { + if (token != NATIVE_TOKEN) { + // ERC20 token + if (from == address(this)) { + TransferHelper.safeTransfer(token, to, fee); + } else { + // safeTransferFrom requires approval + // ruleid: bad-transferfrom-access-control + TransferHelper.safeTransferFrom(token, from, to, fee); + } + } else { + require(from == address(this), "can only transfer eth from the router address"); + + // Native ether + (bool success,) = to.call{value: fee}(""); + require(success, "transfer failed in transferFee"); + } + return fee; + } else { + return 0; + } + } + + function func19(address from, address to) external { + _func20(from, to, amount); + } + + function _func20(address from, address to) internal { + // todoruleid: bad-transferfrom-access-control + usdc.safeTransferFrom(from, to, amount); + } + + function _func21(address from, address to) internal { + // internal never called + // ok: bad-transferfrom-access-control + usdc.safeTransferFrom(from, to, amount); + // ok: bad-transferfrom-access-control + usdc.transferFrom(from, to, amount); + // ok: bad-transferfrom-access-control + Helper.safeTransferFrom(token, from, to, amount); + // ok: bad-transferfrom-access-control + Helper.transferFrom(token, from, to, amount); + } + + function func22(address from, address to) external { + // ok: bad-transferfrom-access-control + usdc.safeTransferFrom(from, from, amount); + } + + function func23(address to, address from) external { + // ruleid: bad-transferfrom-access-control + usdc.safeTransferFrom(from, to, amount); + } + + function transferFrom(address to, address from, uint256 amount) external { + // ok: bad-transferfrom-access-control + super.transferFrom(from, to, amount); + } + + function stakeForAccount(address _fundingAccount, address _account, address _depositToken, uint256 _amount) external override nonReentrant { + _validateHandler(); + _stake(_fundingAccount, _account, _depositToken, _amount); + } + + function _stake(address _fundingAccount, address _account, address _depositToken, uint256 _amount) private { + require(_amount > 0, "RewardTracker: invalid _amount"); + require(isDepositToken[_depositToken], "RewardTracker: invalid _depositToken"); + + // ok: bad-transferfrom-access-control + IERC20(_depositToken).transferFrom(_fundingAccount, address(this), _amount); + + _updateRewards(_account); + + stakedAmounts[_account] = stakedAmounts[_account] + _amount; + depositBalances[_account][_depositToken] = depositBalances[_account][_depositToken] + _amount; + totalDepositSupply[_depositToken] = totalDepositSupply[_depositToken] + _amount; + + _mint(_account, _amount); + } + + + function func24(address from, address to) onlyOwner public { + // ok: bad-transferfrom-access-control + usdc.safeTransferFrom(from, to, amount); + } + + function func25(address from, address to) public { + // ok: bad-transferfrom-access-control + usdc.safeTransferFrom(from, address(this), amount); + } + + function transferIn( + address _token, + address _sender, + uint256 _amount + ) public onlyGame onlyWhitelistedToken(_token) { + // ok: bad-transferfrom-access-control + IERC20(_token).safeTransferFrom(_sender, address(this), _amount); + } + + function func26(address random, address from, address to) public { + // ok: bad-transferfrom-access-control + usdc.safeTransferFrom(from, someaddress, amount); + } + + function func28(address random, address from, address to) public { + // ok: bad-transferfrom-access-control + usdc.safeTransferFrom(this, some, from); + } + + function func29(address random, address from, address token, address onemore) public { + // ok: bad-transferfrom-access-control + IERC20(token).safeTransferFrom(this, some, amount); + } +} + diff --git a/semgrep-rules/security/bad-transferfrom-access-control.yaml b/semgrep-rules/security/bad-transferfrom-access-control.yaml new file mode 100644 index 0000000..549b81a --- /dev/null +++ b/semgrep-rules/security/bad-transferfrom-access-control.yaml @@ -0,0 +1,40 @@ +rules: + - id: bad-transferfrom-access-control + languages: + - solidity + severity: ERROR + message: An attacker may perform transferFrom() with arbitrary addresses + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: LOW + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://app.blocksec.com/explorer/tx/eth/0x54f659773dae6e01f83184d4b6d717c7f1bb71c0aa59e8c8f4a57c25271424b3 #YODL hack + mode: taint + options: + taint_unify_mvars: true + pattern-sources: + - patterns: + - pattern-either: + - pattern: function $F(..., address $FROM, ..., address $TO, ...) external { ... } + - pattern: function $F(..., address $FROM, ..., address $TO, ...) public { ... } + - pattern: function $F(..., address $TO, ..., address $FROM, ...) external { ... } + - pattern: function $F(..., address $TO, ..., address $FROM, ...) public { ... } + - focus-metavariable: + - $FROM + - $TO + - pattern-not: function $F(...) onlyOwner { ... } + pattern-sinks: + - patterns: + - pattern-either: + - pattern: $TOKEN.transferFrom($FROM,$TO,$AMOUNT); + - pattern: $TOKEN.safeTransferFrom($FROM,$TO,$AMOUNT); + - pattern: $HELPER.transferFrom($TOKEN,$FROM,$TO,...); + - pattern: $HELPER.safeTransferFrom($TOKEN,$FROM,$TO,...); + - pattern-not: super.$FUN(...); diff --git a/semgrep-rules/security/balancer-readonly-reentrancy-getpooltokens.sol b/semgrep-rules/security/balancer-readonly-reentrancy-getpooltokens.sol new file mode 100644 index 0000000..3b88679 --- /dev/null +++ b/semgrep-rules/security/balancer-readonly-reentrancy-getpooltokens.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract BALBBA3USDOracle is IOracle, IOracleValidate { + + function check() internal view { + // ok: balancer-readonly-reentrancy-getpooltokens + (address[] memory poolTokens, , ) = getVault().getPoolTokens(getPoolId()); + } + function test() view { + // ruleid: balancer-readonly-reentrancy-getpooltokens + (, uint256[] memory balances, ) = IVault(VAULT_ADDRESS).getPoolTokens(poolId); + } + + function getPrice(address token) external view returns (uint) { + ( + address[] memory poolTokens, + uint256[] memory balances, + // ruleid: balancer-readonly-reentrancy-getpooltokens + ) = vault.getPoolTokens(IPool(token).getPoolId()); + + uint256[] memory weights = IPool(token).getNormalizedWeights(); + + uint length = weights.length; + uint temp = 1e18; + uint invariant = 1e18; + for(uint i; i < length; i++) { + temp = temp.mulDown( + (oracleFacade.getPrice(poolTokens[i]).divDown(weights[i])) + .powDown(weights[i]) + ); + invariant = invariant.mulDown( + (balances[i] * 10 ** (18 - IERC20(poolTokens[i]).decimals())) + .powDown(weights[i]) + ); + } + return invariant + .mulDown(temp) + .divDown(IPool(token).totalSupply()); + } +} + +abstract contract LinearPool { + function check() internal view { + // ok: balancer-readonly-reentrancy-getpooltokens + (, uint256[] memory registeredBalances, ) = getVault().getPoolTokens(getPoolId()); + } +} + +contract Sentiment { + + function checkReentrancy() internal { + vault.manageUserBalance(new IVault.UserBalanceOp[](0)); + } + + function getPrice(address token) external returns (uint) { + checkReentrancy(); + ( + address[] memory poolTokens, + uint256[] memory balances, + // ok: balancer-readonly-reentrancy-getpooltokens + ) = vault.getPoolTokens(IPool(token).getPoolId()); + + //... + } +} + +contract Testing { + + function getPrice(address token) external returns (uint) { + + ( + address[] memory poolTokens, + uint256[] memory balances, + // ok: balancer-readonly-reentrancy-getpooltokens + ) = vault.getPoolTokens(IPool(token).getPoolId()); + + vault.manageUserBalance(new IVault.UserBalanceOp[](0)); + + //... + } +} + +contract TestingSecondCase { + + function checkReentrancy() internal { + VaultReentrancyLib.ensureNotInVaultContext(getVault()); + } + + function getPrice(address token) external returns (uint) { + checkReentrancy(); + + ( + address[] memory poolTokens, + uint256[] memory balances, + // ok: balancer-readonly-reentrancy-getpooltokens + ) = vault.getPoolTokens(IPool(token).getPoolId()); + + //... + } + + function getPrice2(address token) external returns (uint) { + + ( + address[] memory poolTokens, + uint256[] memory balances, + // ruleid: balancer-readonly-reentrancy-getpooltokens + ) = vault.getPoolTokens(IPool(token).getPoolId()); + + //... + } + + function getPrice3(address token) external returns (uint) { + VaultReentrancyLib.ensureNotInVaultContext(getVault()); + ( + address[] memory poolTokens, + uint256[] memory balances, + // ok: balancer-readonly-reentrancy-getpooltokens + ) = vault.getPoolTokens(IPool(token).getPoolId()); + + //... + } +} \ No newline at end of file diff --git a/semgrep-rules/security/balancer-readonly-reentrancy-getpooltokens.yaml b/semgrep-rules/security/balancer-readonly-reentrancy-getpooltokens.yaml new file mode 100644 index 0000000..b2052fa --- /dev/null +++ b/semgrep-rules/security/balancer-readonly-reentrancy-getpooltokens.yaml @@ -0,0 +1,144 @@ +rules: + - id: balancer-readonly-reentrancy-getpooltokens + message: $VAULT.getPoolTokens() call on a Balancer pool is not protected from the read-only reentrancy. + metadata: + category: security + technology: + - solidity + cwe: "CWE-841: Improper Enforcement of Behavioral Workflow" + confidence: HIGH + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://quillaudits.medium.com/decoding-sentiment-protocols-1-million-exploit-quillaudits-f36bee77d376 + - https://hackmd.io/@sentimentxyz/SJCySo1z2 + patterns: + - pattern-either: + - pattern: | + function $F(...) { + ... + $RETURN = $VAULT.getPoolTokens(...); + ... + } + - metavariable-pattern: + metavariable: $RETURN + pattern-regex: .*uint256\[].* + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + VaultReentrancyLib.ensureNotInVaultContext(...); + ... + } + ... + function $F(...) { + ... + $CHECKFUNC(...); + ... + $RETURN = $VAULT.getPoolTokens(...); + ... + } + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + VaultReentrancyLib.ensureNotInVaultContext(...); + ... + } + ... + function $F(...) { + ... + $RETURN = $VAULT.getPoolTokens(...); + ... + $CHECKFUNC(...); + ... + } + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + $VAULT.manageUserBalance(...); + ... + } + ... + function $F(...) { + ... + $RETURN = $VAULT.getPoolTokens(...); + ... + $CHECKFUNC(...); + ... + } + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + $VAULT.manageUserBalance(...); + ... + } + ... + function $F(...) { + ... + $CHECKFUNC(...); + ... + $RETURN = $VAULT.getPoolTokens(...); + ... + } + ... + } + - pattern-not: | + function $F(...) { + ... + VaultReentrancyLib.ensureNotInVaultContext(...); + ... + } + - pattern-not: | + function $F(...) { + ... + $VAULT.manageUserBalance(...); + ... + } + - pattern-not-inside: | + contract LinearPool { + ... + } + - pattern-not-inside: | + contract ComposableStablePool { + ... + } + - pattern-not-inside: | + contract BalancerQueries { + ... + } + - pattern-not-inside: | + contract ManagedPool { + ... + } + - pattern-not-inside: | + contract BaseWeightedPool { + ... + } + - pattern-not-inside: | + contract ComposableStablePoolStorage { + ... + } + - pattern-not-inside: | + contract RecoveryModeHelper { + ... + } + - focus-metavariable: + - $VAULT + languages: + - solidity + severity: ERROR \ No newline at end of file diff --git a/semgrep-rules/security/balancer-readonly-reentrancy-getrate.sol b/semgrep-rules/security/balancer-readonly-reentrancy-getrate.sol new file mode 100644 index 0000000..a900e11 --- /dev/null +++ b/semgrep-rules/security/balancer-readonly-reentrancy-getrate.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract BALBBA3USDOracle is IOracle, IOracleValidate { + + function _get() internal view returns (uint256) { + uint256 usdcLinearPoolPrice = _getLinearPoolPrice(BAL_BB_A3_USDC); + uint256 usdtLinearPoolPrice = _getLinearPoolPrice(BAL_BB_A3_USDT); + uint256 daiLinearPoolPrice = _getLinearPoolPrice(BAL_BB_A3_DAI); + + uint256 minValue = Math.min( + Math.min(usdcLinearPoolPrice, usdtLinearPoolPrice), + daiLinearPoolPrice + ); + // ruleid: balancer-readonly-reentrancy-getrate + return (BAL_BB_A3_USD.getRate() * minValue) / 1e18; + } + + function check() internal view returns (uint256) { + + VaultReentrancyLib.ensureNotInVaultContext(IVault(BALANCER_VAULT)); + // ok: balancer-readonly-reentrancy-getrate + return (BAL_BB_A3_USD.getRate() * minValue) / 1e18; + } + +} + +contract PoolRecoveryHelper is SingletonAuthentication { + + function _updateTokenRateCache( + uint256 index, + IRateProvider provider, + uint256 duration + ) internal virtual { + // ok: balancer-readonly-reentrancy-getrate + uint256 rate = provider.getRate(); + bytes32 cache = _tokenRateCaches[index]; + + _tokenRateCaches[index] = cache.updateRateAndDuration(rate, duration); + + emit TokenRateCacheUpdated(index, rate); + } +} + + +contract TestA { + function checkReentrancy() { + VaultReentrancyLib.ensureNotInVaultContext(IVault(BALANCER_VAULT)); + } + + function test() internal view returns (uint256) { + checkReentrancy(); + // ok: balancer-readonly-reentrancy-getrate + return (BAL_BB_A3_USD.getRate() * minValue) / 1e18; + } + + function test2() internal view returns (uint256) { + + // ruleid: balancer-readonly-reentrancy-getrate + return (BAL_BB_A3_USD.getRate() * minValue) / 1e18; + } +} + +contract TestB { + function checkReentrancy() { + vault.manageUserBalance(new IVault.UserBalanceOp[](0)); + } + + function test() internal view returns (uint256) { + checkReentrancy(); + // ok: balancer-readonly-reentrancy-getrate + return (BAL_BB_A3_USD.getRate() * minValue) / 1e18; + } + + function test2() internal view returns (uint256) { + vault.manageUserBalance(new IVault.UserBalanceOp[](0)); + // ok: balancer-readonly-reentrancy-getrate + return (BAL_BB_A3_USD.getRate() * minValue) / 1e18; + } +} \ No newline at end of file diff --git a/semgrep-rules/security/balancer-readonly-reentrancy-getrate.yaml b/semgrep-rules/security/balancer-readonly-reentrancy-getrate.yaml new file mode 100644 index 0000000..e1d6b9c --- /dev/null +++ b/semgrep-rules/security/balancer-readonly-reentrancy-getrate.yaml @@ -0,0 +1,126 @@ +rules: + - id: balancer-readonly-reentrancy-getrate + message: $VAR.getRate() call on a Balancer pool is not protected from the read-only reentrancy. + metadata: + category: security + technology: + - solidity + cwe: "CWE-841: Improper Enforcement of Behavioral Workflow" + confidence: HIGH + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://forum.balancer.fi/t/reentrancy-vulnerability-scope-expanded/4345 + patterns: + - pattern: | + function $F(...) { + ... + $VAR.getRate(); + ... + } + - pattern-not-inside: | + function $F(...) { + ... + VaultReentrancyLib.ensureNotInVaultContext(...); + ... + } + - pattern-not-inside: | + function $F(...) { + ... + $VAULT.manageUserBalance(...); + ... + } + - pattern-not-inside: | + function _updateTokenRateCache(...) { + ... + } + - pattern-not-inside: | + contract PoolRecoveryHelper { + ... + } + - pattern-not-inside: | + contract ComposableStablePoolRates { + ... + } + - pattern-not-inside: | + contract WeightedPoolProtocolFees { + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + VaultReentrancyLib.ensureNotInVaultContext(...); + ... + } + ... + function $F(...) { + ... + $CHECKFUNC(...); + ... + $VAR.getRate(); + ... + } + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + VaultReentrancyLib.ensureNotInVaultContext(...); + ... + } + ... + function $F(...) { + ... + $VAR.getRate(); + ... + $CHECKFUNC(...); + ... + } + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + $VAULT.manageUserBalance(...); + ... + } + ... + function $F(...) { + ... + $VAR.getRate(); + ... + $CHECKFUNC(...); + ... + } + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + $VAULT.manageUserBalance(...); + ... + } + ... + function $F(...) { + ... + $CHECKFUNC(...); + ... + $VAR.getRate(); + ... + } + ... + } + - focus-metavariable: $VAR + languages: + - solidity + severity: ERROR \ No newline at end of file diff --git a/semgrep-rules/security/basic-arithmetic-underflow.sol b/semgrep-rules/security/basic-arithmetic-underflow.sol new file mode 100644 index 0000000..eda2f4e --- /dev/null +++ b/semgrep-rules/security/basic-arithmetic-underflow.sol @@ -0,0 +1,434 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.7.5; + +import "@openzeppelin/contracts/math/Math.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; + +// Inheritance +import "../interfaces/IStakingRewards.sol"; +import "../interfaces/Pausable.sol"; +import "../interfaces/RewardsDistributionRecipient.sol"; +import "../interfaces/OnDemandToken.sol"; +import "../interfaces/MintableToken.sol"; + +// based on synthetix +contract StakingRewards is IStakingRewards, RewardsDistributionRecipient, ReentrancyGuard, Pausable { + using SafeERC20 for IERC20; + + struct Times { + uint32 periodFinish; + uint32 rewardsDuration; + uint32 lastUpdateTime; + uint96 totalRewardsSupply; + } + + // ========== STATE VARIABLES ========== // + + uint256 public immutable maxEverTotalRewards; + + IERC20 public immutable rewardsToken; + IERC20 public immutable stakingToken; + + uint256 public rewardRate = 0; + uint256 public rewardPerTokenStored; + + mapping(address => uint256) public userRewardPerTokenPaid; + mapping(address => uint256) public rewards; + + uint256 private _totalSupply; + mapping(address => uint256) private _balances; + + Times public timeData; + bool public stopped; + + // ========== EVENTS ========== // + + event RewardAdded(uint256 reward); + event Staked(address indexed user, uint256 amount); + event Withdrawn(address indexed user, uint256 amount); + event RewardPaid(address indexed user, uint256 reward); + event RewardsDurationUpdated(uint256 newDuration); + event FarmingFinished(); + + // ========== MODIFIERS ========== // + + modifier whenActive() { + require(!stopped, "farming is stopped"); + _; + } + + modifier updateReward(address account) virtual { + uint256 newRewardPerTokenStored = rewardPerToken(); + rewardPerTokenStored = newRewardPerTokenStored; + timeData.lastUpdateTime = uint32(lastTimeRewardApplicable()); + + if (account != address(0)) { + rewards[account] = earned(account); + userRewardPerTokenPaid[account] = newRewardPerTokenStored; + } + + _; + } + + // ========== CONSTRUCTOR ========== // + + constructor( + address _owner, + address _rewardsDistribution, + address _stakingToken, + address _rewardsToken + ) Owned(_owner) { + require(OnDemandToken(_rewardsToken).ON_DEMAND_TOKEN(), "rewardsToken must be OnDemandToken"); + + stakingToken = IERC20(_stakingToken); + rewardsToken = IERC20(_rewardsToken); + rewardsDistribution = _rewardsDistribution; + + timeData.rewardsDuration = 2592000; // 30 days + maxEverTotalRewards = MintableToken(_rewardsToken).maxAllowedTotalSupply(); + } + + // ========== RESTRICTED FUNCTIONS ========== // + + function notifyRewardAmount( + uint256 _reward + ) override virtual external whenActive onlyRewardsDistribution updateReward(address(0)) { + Times memory t = timeData; + uint256 newRewardRate; + + if (block.timestamp >= t.periodFinish) { + newRewardRate = _reward / t.rewardsDuration; + } else { + uint256 remaining = t.periodFinish - block.timestamp; + uint256 leftover = remaining * rewardRate; + newRewardRate = (_reward + leftover) / t.rewardsDuration; + } + + require(newRewardRate != 0, "invalid rewardRate"); + + rewardRate = newRewardRate; + + // always increasing by _reward even if notification is in a middle of period + // because leftover is included + uint256 totalRewardsSupply = timeData.totalRewardsSupply + _reward; + require(totalRewardsSupply <= maxEverTotalRewards, "rewards overflow"); + + timeData.totalRewardsSupply = uint96(totalRewardsSupply); + timeData.lastUpdateTime = uint32(block.timestamp); + timeData.periodFinish = uint32(block.timestamp + t.rewardsDuration); + + emit RewardAdded(_reward); + } + + function setRewardsDuration(uint256 _rewardsDuration) external whenActive onlyOwner { + require(_rewardsDuration != 0, "empty _rewardsDuration"); + + require( + block.timestamp > timeData.periodFinish, + "Previous period must be complete before changing the duration" + ); + + timeData.rewardsDuration = uint32(_rewardsDuration); + emit RewardsDurationUpdated(_rewardsDuration); + } + + // when farming was started with 1y and 12tokens + // and we want to finish after 4 months, we need to end up with situation + // like we were starting with 4mo and 4 tokens. + function finishFarming() virtual external whenActive onlyOwner { + Times memory t = timeData; + require(block.timestamp < t.periodFinish, "can't stop if not started or already finished"); + + stopped = true; + + if (_totalSupply != 0) { + uint256 remaining = t.periodFinish - block.timestamp; + timeData.rewardsDuration = uint32(t.rewardsDuration - remaining); + } + + timeData.periodFinish = uint32(block.timestamp); + + emit FarmingFinished(); + } + + // ========== MUTATIVE FUNCTIONS ========== // + + function exit() override external { + withdraw(_balances[msg.sender]); + getReward(); + } + + function stake(uint256 amount) override external { + _stake(msg.sender, amount, false); + } + + function rescueToken(ERC20 _token, address _recipient, uint256 _amount) external onlyOwner() { + if (address(_token) == address(stakingToken)) { + // ruleid: basic-arithmetic-underflow + require(_totalSupply <= stakingToken.balanceOf(address(this)) - _amount, "amount is too big to rescue"); + } else if (address(_token) == address(rewardsToken)) { + revert("reward token can not be rescued"); + } + + _token.transfer(_recipient, _amount); + } + + function periodFinish() external view returns (uint256) { + return timeData.periodFinish; + } + + function rewardsDuration() external view returns (uint256) { + return timeData.rewardsDuration; + } + + function lastUpdateTime() external view returns (uint256) { + return timeData.lastUpdateTime; + } + + function balanceOf(address account) override external view returns (uint256) { + return _balances[account]; + } + + function getRewardForDuration() override external view returns (uint256) { + return rewardRate * timeData.rewardsDuration; + } + + function version() external pure virtual returns (uint256) { + return 1; + } + + function withdraw(uint256 amount) override public { + _withdraw(amount, msg.sender, msg.sender); + } + + function getReward() override public { + _getReward(msg.sender, msg.sender); + } + + // ========== VIEWS ========== // + + function totalSupply() override public view returns (uint256) { + return _totalSupply; + } + + function lastTimeRewardApplicable() override public view returns (uint256) { + return Math.min(block.timestamp, timeData.periodFinish); + } + + function rewardPerToken() override public view returns (uint256) { + if (_totalSupply == 0) { + return rewardPerTokenStored; + } + + return rewardPerTokenStored + ( + (lastTimeRewardApplicable() - timeData.lastUpdateTime) * rewardRate * 1e18 / _totalSupply + ); + } + + function earned(address account) override virtual public view returns (uint256) { + return (_balances[account] * (rewardPerToken() - userRewardPerTokenPaid[account]) / 1e18) + rewards[account]; + } + + function _stake(address user, uint256 amount, bool migration) + internal + nonReentrant + notPaused + updateReward(user) + { + require(timeData.periodFinish != 0, "Stake period not started yet"); + require(amount != 0, "Cannot stake 0"); + + _totalSupply = _totalSupply + amount; + _balances[user] = _balances[user] + amount; + + if (migration) { + // other contract will send tokens to us, this will save ~13K gas + } else { + // not using safe transfer, because we working with trusted tokens + require(stakingToken.transferFrom(user, address(this), amount), "token transfer failed"); + } + + emit Staked(user, amount); + } + + /// @param amount tokens to withdraw + /// @param user address + /// @param recipient address, where to send tokens, if we migrating token address can be zero + function _withdraw(uint256 amount, address user, address recipient) internal nonReentrant updateReward(user) { + require(amount != 0, "Cannot withdraw 0"); + + // not using safe math, because there is no way to overflow if stake tokens not overflow + // ruleid: basic-arithmetic-underflow + _totalSupply = _totalSupply - amount; + // ruleid: basic-arithmetic-underflow + _balances[user] = _balances[user] - amount; + // not using safe transfer, because we working with trusted tokens + require(stakingToken.transfer(recipient, amount), "token transfer failed"); + + emit Withdrawn(user, amount); + } + + /// @param user address + /// @param recipient address, where to send reward + function _getReward(address user, address recipient) + internal + virtual + nonReentrant + updateReward(user) + returns (uint256 reward) + { + reward = rewards[user]; + + if (reward != 0) { + rewards[user] = 0; + OnDemandToken(address(rewardsToken)).mint(recipient, reward); + emit RewardPaid(user, reward); + } + } +} + +// Remittance Token + +contract RemcoToken is Token, Owned { + using SafeMath for uint256; + + uint public _totalSupply; + + string public name; //The Token's name + + uint8 public constant decimals = 8; //Number of decimals of the smallest unit + + string public symbol; //The Token's symbol + + uint256 public mintCount; + + uint256 public deleteToken; + + uint256 public soldToken; + + + mapping (address => uint256) public balanceOf; + + // Owner of account approves the transfer of an amount to another account + mapping(address => mapping(address => uint256)) allowed; + + + + // Constructor + function RemcoToken(string coinName,string coinSymbol,uint initialSupply) { + _totalSupply = initialSupply *10**uint256(decimals); // Update total supply + balanceOf[msg.sender] = _totalSupply; + name = coinName; // Set the name for display purposes + symbol =coinSymbol; + + } + + function totalSupply() public returns (uint256 totalSupply) { + return _totalSupply; + } + + // Send back ether sent to me + function () { + revert(); + } + + // Transfer the balance from owner's account to another account + function transfer(address _to, uint256 _amount) returns (bool success) { + // according to AssetToken's total supply, never overflow here + if (balanceOf[msg.sender] >= _amount + && _amount > 0) { + // todoruleid: basic-arithmetic-underflow + balanceOf[msg.sender] -= uint112(_amount); + balanceOf[_to] = _amount.add(balanceOf[_to]).toUINT112(); + soldToken = _amount.add(soldToken).toUINT112(); + Transfer(msg.sender, _to, _amount); + return true; + } else { + return false; + } + } + + + function transferFrom( + address _from, + address _to, + uint256 _amount + ) returns (bool success) { + // according to AssetToken's total supply, never overflow here + if (balanceOf[_from] >= _amount + && allowed[_from][msg.sender] >= _amount + && _amount > 0) { + balanceOf[_from] = balanceOf[_from].sub(_amount).toUINT112(); + // todoruleid: basic-arithmetic-underflow + allowed[_from][msg.sender] -= _amount; + balanceOf[_to] = _amount.add(balanceOf[_to]).toUINT112(); + Transfer(_from, _to, _amount); + return true; + } else { + return false; + } + } + + + function approve(address _spender, uint256 _amount) returns (bool success) { + allowed[msg.sender][_spender] = _amount; + Approval(msg.sender, _spender, _amount); + return true; + } + + + + function allowance(address _owner, address _spender) constant returns (uint256 remaining) { + return allowed[_owner][_spender]; + } + + //Mint tokens and assign to some one + function mint(address _owner, uint256 _amount) onlyOwner{ + + balanceOf[_owner] = _amount.add(balanceOf[_owner]).toUINT112(); + mintCount = _amount.add(mintCount).toUINT112(); + _totalSupply = _totalSupply.add(_amount).toUINT112(); + } + //Burn tokens from owner account + function burn(uint256 _count) public returns (bool success) + { + // ruleid: basic-arithmetic-underflow + balanceOf[msg.sender] -=uint112( _count); + deleteToken = _count.add(deleteToken).toUINT112(); + _totalSupply = _totalSupply.sub(_count).toUINT112(); + Burn(msg.sender, _count); + return true; + } + + function exp1(uint255 a) public { + uint256 b = 1337; + // ruleid: basic-arithmetic-underflow + uint256 c = b - a + } + + function exp2(uint255 a) public { + exp3(a); + } + + function exp3(uint255 a) intenal { + uint256 b = 1337; + // ruleid: basic-arithmetic-underflow + uint256 c = b - a; + } + + function ok1(address s) public { + uint256 h = 1338; + uint256 g = 256; + // ok: basic-arithmetic-underflow + h -= g; + } + + function ok2(uint255 a) internal { + uint256 y = 1337; + // ok: basic-arithmetic-underflow + uint256 x = y - a; + } + + } \ No newline at end of file diff --git a/semgrep-rules/security/basic-arithmetic-underflow.yaml b/semgrep-rules/security/basic-arithmetic-underflow.yaml new file mode 100644 index 0000000..0dadb18 --- /dev/null +++ b/semgrep-rules/security/basic-arithmetic-underflow.yaml @@ -0,0 +1,35 @@ +rules: + - + id: basic-arithmetic-underflow + message: Possible arithmetic underflow + metadata: + category: security + technology: + - solidity + cwe: "CWE-191: Integer Underflow (Wrap or Wraparound)" + confidence: LOW + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://medium.com/@Knownsec_Blockchain_Lab/knownsec-blockchain-lab-umbnetwork-attack-event-analysis-9bae1141e58 + - https://twitter.com/danielvf/status/1497194778278174724 + - https://etherscan.io/address/0xbbc3a290c7d2755b48681c87f25f9d7f480ad42f # Remittance + mode: taint + pattern-sinks: + - pattern: $Y - $X + - pattern: $Y -= $X + pattern-sources: + - patterns: + - pattern-inside: | + function $F(..., $TYPE $X, ...) public { ... } + - focus-metavariable: $X + - patterns: + - pattern-inside: | + function $F(..., $TYPE $X, ...) external { ... } + - focus-metavariable: $X + languages: + - solidity + severity: INFO + diff --git a/semgrep-rules/security/basic-oracle-manipulation.sol b/semgrep-rules/security/basic-oracle-manipulation.sol new file mode 100644 index 0000000..d4f543f --- /dev/null +++ b/semgrep-rules/security/basic-oracle-manipulation.sol @@ -0,0 +1,506 @@ +pragma solidity 0.6.12; + +import "@openzeppelin/contracts/math/Math.sol"; +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import "./interface/IStrategy.sol"; +import "hardhat/console.sol"; + +contract OneRingVault is ERC20Upgradeable, OwnableUpgradeable { + using SafeERC20Upgradeable for IERC20Upgradeable; + using SafeERC20 for IERC20; + using SafeMath for Math; + + address public activeStrategy; + + address[] public underlyings; + mapping(address => bool) public underlyingEnabled; + + uint256 public underlyingUnit; + + event Withdraw( + address indexed beneficiary, + uint256 amount, + address indexed underlying + ); + event Deposit( + address indexed beneficiary, + uint256 amount, + address indexed underlying + ); + event Invest( + uint256 amount, + address indexed underlying, + address indexed strategy + ); + + address public treasury; + uint256 public performanceFee; + uint256 public performanceFeeMax; + + constructor() public {} + + function initialize(address[] memory _underlyings) public initializer { + __ERC20_init("One Ring Share", "OShare"); + _setupDecimals(18); + __Ownable_init(); + + underlyingUnit = 10**18; + + for (uint256 _ui = 0; _ui < _underlyings.length; _ui++) { + _addUnderlying(_underlyings[_ui]); + } + } + + function _doHardWork(uint256 _amount, address _token) internal { + _invest(_amount, _token); + IStrategy(activeStrategy).doHardWork(); + } + + function doHardWork(uint256 _amount, address _token) external onlyOwner { + _doHardWork(_amount, _token); + } + + function _doHardWorkAll() internal { + for (uint256 i = 0; i < underlyings.length; i++) { + uint256 _amount = IERC20(underlyings[i]).balanceOf(address(this)); + _invest(_amount, underlyings[i]); + } + IStrategy(activeStrategy).doHardWork(); + } + + function doHardWorkAll() external onlyOwner { + _doHardWorkAll(); + } + + function _invest(uint256 _amount, address _token) internal { + uint256 _availableAmount = IERC20(_token).balanceOf(address(this)); + require(_amount <= _availableAmount, "not insufficient amount"); + if (_amount > 0) { + IERC20(_token).safeTransfer(address(activeStrategy), _amount); + IStrategy(activeStrategy).assetToUnderlying(_token); + emit Invest(_amount, _token, activeStrategy); + } + } + + function invest(uint256 _amount, address _token) public onlyOwner { + _invest(_amount, _token); + } + + function balanceWithInvested() public view returns (uint256 balance) { + balance = IStrategy(activeStrategy).investedBalanceInUSD(); + } + function getSharePrice() public view returns (uint256 _sharePrice) { + // ruleid: basic-oracle-manipulation + _sharePrice = totalSupply() == 0 + ? underlyingUnit + : underlyingUnit.mul(balanceWithInvested()).div(totalSupply()); + + if (_sharePrice < underlyingUnit) { + _sharePrice = underlyingUnit; + } + } + + function _getDecimaledUnderlyingBalance(address _underlying) + internal + returns (uint256 _balance) + { + _balance = IERC20(_underlying) + .balanceOf(address(this)) + .mul(uint256(10)**uint256(decimals())) + .div(uint256(10)**uint256(ERC20(_underlying).decimals())); + } + + function _getDefaultMinimumAmountForDeposit(uint256 _amount, address _token) + internal + returns (uint256 _minAmount) + { + uint256 _sharePrice = getSharePrice(); + uint256 _amountInUsd = _amount + .mul(uint256(10)**uint256(decimals())) + .div(uint256(10)**uint256(ERC20(_token).decimals())); + _minAmount = _amountInUsd.mul(underlyingUnit).div(_sharePrice); + _minAmount = _minAmount.mul(98).div(100); + } + + function deposit(uint256 _amount, address _token) external { + uint256 _minAmount = _getDefaultMinimumAmountForDeposit( + _amount, + _token + ); + _deposit(_amount, _token, msg.sender, _minAmount); + } + + function depositSafe( + uint256 _amount, + address _token, + uint256 _minAmount + ) external { + _deposit(_amount, _token, msg.sender, _minAmount); + } + + function _deposit( + uint256 _amount, + address _underlying, + address _sender, + uint256 _minAmount + ) internal { + require(_amount > 0, "Cannot deposit 0"); + require( + underlyingEnabled[_underlying], + "Underlying token is not enabled" + ); + + uint256 _sharePrice = getSharePrice(); + + IERC20(_underlying).safeTransferFrom(_sender, activeStrategy, _amount); + uint256 _newLiquidityInUSD = IStrategy(activeStrategy) + .assetToUnderlying(_underlying); + + uint256 _amountInUSD = _amount + .mul(uint256(10)**uint256(decimals())) + .div(uint256(10)**uint256(ERC20(_underlying).decimals())); + + if (_newLiquidityInUSD > _amountInUSD) { + _newLiquidityInUSD = _amountInUSD; + } + + _doHardWorkAll(); + + uint256 _toMint = _newLiquidityInUSD.mul(underlyingUnit).div( + _sharePrice + ); + _mint(_sender, _toMint); + + require(_toMint >= _minAmount, "Mint amount is too small."); + + emit Deposit(_sender, _amount, _underlying); + } + + function withdrawAll(address _underlying) external onlyOwner { + _withdrawAll(_underlying); + } + + function _withdrawAll(address _underlying) internal { + IStrategy(activeStrategy).withdrawAllToVault(_underlying); + } + + function withdraw(uint256 _amount, address _underlying) + external + returns (uint256) + { + // if slippage is not set, set it to 2 percent + uint256 _sharePrice = getSharePrice(); + uint256 _amountInUsd = _amount.mul(_sharePrice).div(underlyingUnit); + uint256 _minAmountInUsd = _amountInUsd.mul(98).div(100); + uint256 _minAmount = _minAmountInUsd + .mul(uint256(10)**uint256(ERC20(_underlying).decimals())) + .div(uint256(10)**uint256(decimals())); + return _withdraw(_amount, msg.sender, _underlying, _minAmount); + } + + function withdrawSafe( + uint256 _amount, + address _underlying, + uint256 _minAmount + ) public returns (uint256) { + return _withdraw(_amount, msg.sender, _underlying, _minAmount); + } + + function _withdraw( + uint256 _amount, + address _sender, + address _underlying, + uint256 _minAmount + ) internal returns (uint256) { + require(totalSupply() > 0, "Vault has no shares"); + require(_amount > 0, "Shares must be greater than 0"); + require(underlyingEnabled[_underlying], "Underlying is not enabled"); + + uint256 _totalSupply = totalSupply(); + _burn(_sender, _amount); + + uint256 _toWithdraw = balanceWithInvested().mul(_amount).div( + _totalSupply + ); + uint256 _realWithdraw; + uint256 _stakedWithdraw; + + uint256 _underlyingBal = _getDecimaledUnderlyingBalance(_underlying); + if (_toWithdraw <= _underlyingBal) { + _realWithdraw = _toWithdraw + .mul(uint256(10)**uint256(ERC20(_underlying).decimals())) + .div(uint256(10)**uint256(decimals())); + IERC20(_underlying).safeTransferFrom( + address(this), + _sender, + _realWithdraw + ); + emit Withdraw(_sender, _realWithdraw, _underlying); + return _realWithdraw; + } else { + _stakedWithdraw = _underlyingBal; + } + + uint256 _missing = _toWithdraw.sub(_stakedWithdraw); + IStrategy(activeStrategy).withdrawToVault(_missing, _underlying); + + _realWithdraw = IERC20(_underlying).balanceOf(address(this)); + + require( + _realWithdraw >= _minAmount, + "Withdraw amount is less than mininum amount." + ); + + IERC20(_underlying).safeTransfer(_sender, _realWithdraw); + emit Withdraw(_sender, _realWithdraw, _underlying); + return _realWithdraw; + } + + function _addUnderlying(address _underlying) internal { + require(_underlying != address(0), "_underlying must be defined"); + underlyings.push(_underlying); + underlyingEnabled[_underlying] = true; + } + + function enableUnderlying(address _underlying) public onlyOwner { + require(_underlying != address(0), "_underlying must be defined"); + underlyingEnabled[_underlying] = true; + } + + function disableUnderlying(address _underlying) public onlyOwner { + require(_underlying != address(0), "_underlying must be defined"); + underlyingEnabled[_underlying] = false; + } + + function setActiveStrategy(address _strategy) public onlyOwner { + require(_strategy != address(0), "strategy must be defined"); + activeStrategy = _strategy; + } + + function migrateStrategy( + address _oldStrategy, + address _newStrategy, + address _underlying, + uint256 _usdAmount + ) external onlyOwner { + require(_underlying != address(0), "underlying must be defined"); + require(_oldStrategy != address(0), "Old strategy must be defined"); + require(_newStrategy != address(0), "New strategy must be defined"); + require( + IStrategy(activeStrategy).strategyEnabled(_newStrategy), + "New strategy must be enabled." + ); + + uint256 _underlyingAmountBefore = IERC20(_underlying).balanceOf( + address(this) + ); + IStrategy(_oldStrategy).withdrawToVault(_usdAmount, _underlying); + uint256 _underlyingAmountAfter = IERC20(_underlying).balanceOf( + address(this) + ); + + uint256 _amountToInvest = _underlyingAmountAfter.sub( + _underlyingAmountBefore + ); + IERC20(_underlying).safeTransfer(_newStrategy, _amountToInvest); + IStrategy(_newStrategy).doHardWork(); + } + + function setTreasury(address _treasury) external onlyOwner { + require(_treasury != address(0), "zero address"); + treasury = _treasury; + } + + function setPerformanceFee( + uint256 _performanceFee, + uint256 _performanceFeeMax + ) external onlyOwner { + require(_performanceFee <= _performanceFeeMax, "not valid fee values"); + performanceFee = _performanceFee; + performanceFeeMax = _performanceFeeMax; + } + + function underlyingLength() public view returns (uint256) { + return underlyings.length; + } +} + +// Deus Finance vulnerable oracle + +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 + // ruleid: basic-oracle-manipulation + ((dei.balanceOf(address(pair)) + (usdc.balanceOf(address(pair)) * 1e12)) * + 1e18) / pair.totalSupply(); + } +} + + +// Deus vulnerable again + +// Be name Khoda +// Bime Abolfazl +// SPDX-License-Identifier: MIT + +// ================================================================================================================= +// _|_|_| _|_|_|_| _| _| _|_|_| _|_|_|_| _| | +// _| _| _| _| _| _| _| _|_|_| _|_|_| _|_|_| _|_|_| _|_| | +// _| _| _|_|_| _| _| _|_| _|_|_| _| _| _| _| _| _| _| _| _|_|_|_| | +// _| _| _| _| _| _| _| _| _| _| _| _| _| _| _| _| | +// _|_|_| _|_|_|_| _|_| _|_|_| _| _| _| _| _|_|_| _| _| _|_|_| _|_|_| | +// ================================================================================================================= +// ==================== Oracle =================== +// ============================================== +// DEUS Finance: https://github.com/deusfinance + +// Primary Author(s) +// Mmd: https://github.com/mmd-mostafaee + +pragma solidity 0.8.12; + +import "@openzeppelin/contracts/access/AccessControl.sol"; +import "./interfaces/IERC20.sol"; +import "./interfaces/IMuon.sol"; +import "./interfaces/IBaseV1Pair.sol"; + +/// @title Oracle of DeiLenderLP +/// @author DEUS Finance +/// @notice to provide LP price for DeiLenderLP +contract Oracle is AccessControl { + IERC20 public dei; + IERC20 public usdc; + IERC20 public pair; + + uint256 price; // usdc + + IMuonV02 public muon; + uint32 public appId; + uint256 public minimumRequiredSignatures; + uint256 public expireTime; + uint256 public threshold; + + bytes32 public constant SETTER_ROLE = keccak256("SETTER_ROLE"); + + modifier isSetter() { + require(hasRole(SETTER_ROLE, msg.sender), "Caller is not setter"); + _; + } + + constructor( + IERC20 dei_, + IERC20 usdc_, + IERC20 pair_, + IMuonV02 muon_, + uint32 appId_, + uint256 minimumRequiredSignatures_, + uint256 expireTime_, + uint256 threshold_, + address admin, + address setter + ) { + dei = dei_; + usdc = usdc_; + pair = pair_; + muon = muon_; + appId = appId_; + expireTime = expireTime_; + threshold = threshold_; + + minimumRequiredSignatures = minimumRequiredSignatures_; + + _setupRole(DEFAULT_ADMIN_ROLE, admin); + _setupRole(SETTER_ROLE, setter); + } + + function setMuon(IMuonV02 muon_) external isSetter { + muon = muon_; + } + + function setAppId(uint32 appId_) external isSetter { + appId = appId_; + } + + function setMinimumRequiredSignatures(uint256 minimumRequiredSignatures_) + external + isSetter + { + minimumRequiredSignatures = minimumRequiredSignatures_; + } + + function setExpireTime(uint256 expireTime_) external isSetter { + expireTime = expireTime_; + } + + function setThreshold(uint256 threshold_) external isSetter { + threshold = threshold_; + } + + /// @notice returns on chain LP price + function getOnChainPrice() public view returns (uint256) { + return + // ruleid: basic-oracle-manipulation + ((dei.balanceOf(address(pair)) * IBaseV1Pair(address(pair)).getAmountOut(1e18, address(dei)) * 1e12 / 1e18) + (usdc.balanceOf(address(pair)) * 1e12)) * 1e18 / pair.totalSupply(); + } + + /// @notice returns + function getPrice( + uint256 price, + uint256 timestamp, + bytes calldata reqId, + SchnorrSign[] calldata sigs + ) public returns (uint256) { + require( + timestamp + expireTime >= block.timestamp, + "ORACLE: SIGNATURE_EXPIRED" + ); + + uint256 onChainPrice = getOnChainPrice(); + uint256 diff = onChainPrice < price ? onChainPrice * 1e18 / price : price * 1e18 / onChainPrice; + require( + 1e18 - diff < threshold + ,"ORACLE: PRICE_GAP" + ); + + address[] memory pairs1 = new address[](1); + pairs1[0] = address(pair); + bytes32 hash = keccak256( + abi.encodePacked( + appId, + address(pair), + new address[](0), + pairs1, + price, + timestamp + ) + ); + + require( + muon.verify(reqId, uint256(hash), sigs), + "ORACLE: UNVERIFIED_SIGNATURES" + ); + + return price; + } +} \ No newline at end of file diff --git a/semgrep-rules/security/basic-oracle-manipulation.yaml b/semgrep-rules/security/basic-oracle-manipulation.yaml new file mode 100644 index 0000000..9eb970f --- /dev/null +++ b/semgrep-rules/security/basic-oracle-manipulation.yaml @@ -0,0 +1,49 @@ +rules: + - + id: basic-oracle-manipulation + message: Price oracle can be manipulated via flashloan + metadata: + category: security + technology: + - solidity + cwe: "CWE-682: Incorrect Calculation" + confidence: LOW + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://medium.com/oneringfinance/onering-finance-exploit-post-mortem-after-oshare-hack-602a529db99b + - https://twitter.com/peckshield/status/1506090607059431427 + - https://pwned-no-more.notion.site/The-Deus-Hack-Explained-647bf97afa2b4e4e9e8b882e68a75c0b + - https://twitter.com/peckshield/status/1519530463337250817 + - https://ftmscan.com/address/0xc06826f52f29b34c5d8b2c61abf844cebcf78abf # OneRing + - https://ftmscan.com/address/0x5CEB2b0308a7f21CcC0915DB29fa5095bEAdb48D # Deus + - https://ftmscan.com/address/0x8129026c585bcfa530445a6267f9389057761a00 # Deus (again) + patterns: + - pattern-inside: | + function $F(...) { + ... + } + - pattern-either: + - pattern: $X.div($Y) + - pattern: $X / $Y + - metavariable-regex: + metavariable: $F + regex: (?i)get([a-z0-9_])*price + - metavariable-pattern: + metavariable: $X + pattern-either: + - pattern: underlying + - pattern: underlyingUnit + - pattern: pair + - pattern: reserve + - pattern: reserve0 + - pattern: reserve1 + - metavariable-regex: + metavariable: $Y + regex: .*totalSupply.* + languages: + - solidity + severity: INFO + diff --git a/semgrep-rules/security/compound-borrowfresh-reentrancy.sol b/semgrep-rules/security/compound-borrowfresh-reentrancy.sol new file mode 100644 index 0000000..92f3f1f --- /dev/null +++ b/semgrep-rules/security/compound-borrowfresh-reentrancy.sol @@ -0,0 +1,3386 @@ +pragma solidity ^0.5.16; + +/** + * @title Compound's InterestRateModel Interface + * @author Compound + */ +contract InterestRateModel { + /// @notice Indicator that this is an InterestRateModel contract (for inspection) + bool public constant isInterestRateModel = true; + + /** + * @notice Calculates the current borrow interest rate per block + * @param cash The total amount of cash the market has + * @param borrows The total amount of borrows the market has outstanding + * @param reserves The total amount of reserves the market has + * @return The borrow rate per block (as a percentage, and scaled by 1e18) + */ + function getBorrowRate(uint cash, uint borrows, uint reserves) external view returns (uint); + + /** + * @notice Calculates the current supply interest rate per block + * @param cash The total amount of cash the market has + * @param borrows The total amount of borrows the market has outstanding + * @param reserves The total amount of reserves the market has + * @param reserveFactorMantissa The current reserve factor the market has + * @return The supply rate per block (as a percentage, and scaled by 1e18) + */ + function getSupplyRate(uint cash, uint borrows, uint reserves, uint reserveFactorMantissa) external view returns (uint); + +} + + + + + + + + + + + + +contract ComptrollerErrorReporter { + enum Error { + NO_ERROR, + UNAUTHORIZED, + COMPTROLLER_MISMATCH, + INSUFFICIENT_SHORTFALL, + INSUFFICIENT_LIQUIDITY, + INVALID_CLOSE_FACTOR, + INVALID_COLLATERAL_FACTOR, + INVALID_LIQUIDATION_INCENTIVE, + MARKET_NOT_ENTERED, // no longer possible + MARKET_NOT_LISTED, + MARKET_ALREADY_LISTED, + MATH_ERROR, + NONZERO_BORROW_BALANCE, + PRICE_ERROR, + REJECTION, + SNAPSHOT_ERROR, + TOO_MANY_ASSETS, + TOO_MUCH_REPAY, + + // OLA_ADDITIONS : All Enums from here + NOT_IN_MARKET, + TOO_LITTLE_BORROW, + IN_FRESH_LIQUIDATION_LIMITED_PERIOD, + INVALID_LIQUIDATION_FACTOR, + BORROWED_AGAINST_FAILED, + TOTAL_BORROWED_AGAINST_TOO_HIGH, + TOO_MUCH_COLLATERAL_ACTIVATION, + + // V0.02 + NOT_APPROVED_TO_MINT, + NOT_APPROVED_TO_BORROW + } + + enum FailureInfo { + ACCEPT_ADMIN_PENDING_ADMIN_CHECK, + ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK, + EXIT_MARKET_BALANCE_OWED, + EXIT_MARKET_REJECTION, + SET_CLOSE_FACTOR_OWNER_CHECK, + SET_CLOSE_FACTOR_VALIDATION, + SET_COLLATERAL_FACTOR_OWNER_CHECK, + SET_COLLATERAL_FACTOR_NO_EXISTS, + SET_COLLATERAL_FACTOR_VALIDATION, + SET_COLLATERAL_FACTOR_WITHOUT_PRICE, + SET_IMPLEMENTATION_OWNER_CHECK, + SET_LIQUIDATION_INCENTIVE_OWNER_CHECK, + SET_LIQUIDATION_INCENTIVE_VALIDATION, + SET_MAX_ASSETS_OWNER_CHECK, + SET_PENDING_ADMIN_OWNER_CHECK, + SET_PENDING_IMPLEMENTATION_OWNER_CHECK, + SET_PRICE_ORACLE_OWNER_CHECK, + SUPPORT_MARKET_EXISTS, + SUPPORT_MARKET_OWNER_CHECK, + SET_PAUSE_GUARDIAN_OWNER_CHECK, + + // OLA_ADDITIONS : All Enums from here + SET_LIQUIDATION_INCENTIVE_NO_EXISTS, + SET_LIQUIDATION_INCENTIVE_WITHOUT_PRICE, + SET_LIQUIDATION_FACTOR_OWNER_CHECK, + SET_LIQUIDATION_FACTOR_NO_EXISTS, + SET_LIQUIDATION_FACTOR_VALIDATION, + SET_LIQUIDATION_FACTOR_WITHOUT_PRICE, + SET_LIQUIDATION_FACTOR_LOWER_THAN_COLLATERAL_FACTOR, + SET_LIQUIDATION_FACTOR_LOWER_THAN_EXISTING_FACTOR, + SET_COLLATERAL_FACTOR_HIGHER_THAN_LIQUIDATION_FACTOR, + SET_RAIN_MAKER_OWNER_CHECK, + ENTER_MARKET_NOT_ALLOWED, + UPDATE_LN_VERSION_ADMIN_OWNER_CHECK, + // V0.002 + SET_BOUNCER_OWNER_CHECK, + SET_LIMIT_MINTING_OWNER_CHECK, + SET_LIMIT_BORROWING_OWNER_CHECK, + SET_MIN_BORROW_AMOUNT_USD_OWNER_CHECK, + SUPPORT_NEW_MARKET_OWNER_CHECK, + SUPPORT_NEW_MARKET_COMBINATION_CHECK + } + + /** + * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary + * contract-specific code that enables us to report opaque error codes from upgradeable contracts. + **/ + event Failure(uint error, uint info, uint detail); + + /** + * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator + */ + function fail(Error err, FailureInfo info) internal returns (uint) { + emit Failure(uint(err), uint(info), 0); + + return uint(err); + } + + /** + * @dev use this when reporting an opaque error from an upgradeable collaborator contract + */ + function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) { + emit Failure(uint(err), uint(info), opaqueError); + + return uint(err); + } +} + +contract TokenErrorReporter { + enum Error { + NO_ERROR, + UNAUTHORIZED, + BAD_INPUT, + COMPTROLLER_REJECTION, + COMPTROLLER_CALCULATION_ERROR, + INTEREST_RATE_MODEL_ERROR, + INVALID_ACCOUNT_PAIR, + INVALID_CLOSE_AMOUNT_REQUESTED, + INVALID_COLLATERAL_FACTOR, + MATH_ERROR, + MARKET_NOT_FRESH, + MARKET_NOT_LISTED, + TOKEN_INSUFFICIENT_ALLOWANCE, + TOKEN_INSUFFICIENT_BALANCE, + TOKEN_INSUFFICIENT_CASH, + TOKEN_TRANSFER_IN_FAILED, + TOKEN_TRANSFER_OUT_FAILED, + + // OLA_ADDITIONS : All Enums from here + BAD_SYSTEM_PARAMS + } + + /* + * Notice: FailureInfo (but not Error) is kept in alphabetical order + * This is because FailureInfo grows significantly faster, and + * the order of Error has some meaning, while the order of FailureInfo + * is entirely arbitrary. + */ + enum FailureInfo { + ACCEPT_ADMIN_PENDING_ADMIN_CHECK, + ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED, + ACCRUE_INTEREST_BORROW_RATE_CALCULATION_FAILED, + ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED, + ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED, + ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED, + ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED, + BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED, + BORROW_ACCRUE_INTEREST_FAILED, + BORROW_CASH_NOT_AVAILABLE, + BORROW_FRESHNESS_CHECK, + BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED, + BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED, + BORROW_MARKET_NOT_LISTED, + BORROW_COMPTROLLER_REJECTION, + LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED, + LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED, + LIQUIDATE_COLLATERAL_FRESHNESS_CHECK, + LIQUIDATE_COMPTROLLER_REJECTION, + LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED, + LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX, + LIQUIDATE_CLOSE_AMOUNT_IS_ZERO, + LIQUIDATE_FRESHNESS_CHECK, + LIQUIDATE_LIQUIDATOR_IS_BORROWER, + LIQUIDATE_REPAY_BORROW_FRESH_FAILED, + LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED, + LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED, + LIQUIDATE_SEIZE_COMPTROLLER_REJECTION, + LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER, + LIQUIDATE_SEIZE_TOO_MUCH, + MINT_ACCRUE_INTEREST_FAILED, + MINT_COMPTROLLER_REJECTION, + MINT_EXCHANGE_CALCULATION_FAILED, + MINT_EXCHANGE_RATE_READ_FAILED, + MINT_FRESHNESS_CHECK, + MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED, + MINT_NEW_TOTAL_SUPPLY_CALCULATION_FAILED, + MINT_TRANSFER_IN_FAILED, + MINT_TRANSFER_IN_NOT_POSSIBLE, + REDEEM_ACCRUE_INTEREST_FAILED, + REDEEM_COMPTROLLER_REJECTION, + REDEEM_EXCHANGE_TOKENS_CALCULATION_FAILED, + REDEEM_EXCHANGE_AMOUNT_CALCULATION_FAILED, + REDEEM_EXCHANGE_RATE_READ_FAILED, + REDEEM_FRESHNESS_CHECK, + REDEEM_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED, + REDEEM_NEW_TOTAL_SUPPLY_CALCULATION_FAILED, + REDEEM_TRANSFER_OUT_NOT_POSSIBLE, + REDUCE_RESERVES_ACCRUE_INTEREST_FAILED, + REDUCE_RESERVES_ADMIN_CHECK, + REDUCE_RESERVES_CASH_NOT_AVAILABLE, + REDUCE_RESERVES_FRESH_CHECK, + REDUCE_RESERVES_VALIDATION, + REPAY_BEHALF_ACCRUE_INTEREST_FAILED, + REPAY_BORROW_ACCRUE_INTEREST_FAILED, + REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED, + REPAY_BORROW_COMPTROLLER_REJECTION, + REPAY_BORROW_FRESHNESS_CHECK, + REPAY_BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED, + REPAY_BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED, + REPAY_BORROW_TRANSFER_IN_NOT_POSSIBLE, + SET_COLLATERAL_FACTOR_OWNER_CHECK, + SET_COLLATERAL_FACTOR_VALIDATION, + SET_COMPTROLLER_OWNER_CHECK, + SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED, + SET_INTEREST_RATE_MODEL_FRESH_CHECK, + SET_INTEREST_RATE_MODEL_OWNER_CHECK, + SET_MAX_ASSETS_OWNER_CHECK, + SET_ORACLE_MARKET_NOT_LISTED, + SET_PENDING_ADMIN_OWNER_CHECK, + SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED, + SET_RESERVE_FACTOR_ADMIN_CHECK, + SET_RESERVE_FACTOR_FRESH_CHECK, + SET_RESERVE_FACTOR_BOUNDS_CHECK, + TRANSFER_COMPTROLLER_REJECTION, + TRANSFER_NOT_ALLOWED, + TRANSFER_NOT_ENOUGH, + TRANSFER_TOO_MUCH, + ADD_RESERVES_ACCRUE_INTEREST_FAILED, + ADD_RESERVES_FRESH_CHECK, + ADD_RESERVES_TRANSFER_IN_NOT_POSSIBLE, + + // OLA_ADDITIONS : All Enums from here + REDUCE_RESERVES_OLA_PART_CALCULATION_FAILED + } + + /** + * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary + * contract-specific code that enables us to report opaque error codes from upgradeable contracts. + **/ + event Failure(uint error, uint info, uint detail); + + /** + * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator + */ + function fail(Error err, FailureInfo info) internal returns (uint) { + emit Failure(uint(err), uint(info), 0); + + return uint(err); + } + + /** + * @dev use this when reporting an opaque error from an upgradeable collaborator contract + */ + function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) { + emit Failure(uint(err), uint(info), opaqueError); + + return uint(err); + } +} + + + + +/** + * @title Careful Math + * @author Compound + * @notice Derived from OpenZeppelin's SafeMath library + * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol + */ +contract CarefulMath { + + /** + * @dev Possible error codes that we can return + */ + enum MathError { + NO_ERROR, + DIVISION_BY_ZERO, + INTEGER_OVERFLOW, + INTEGER_UNDERFLOW + } + + /** + * @dev Multiplies two numbers, returns an error on overflow. + */ + function mulUInt(uint a, uint b) internal pure returns (MathError, uint) { + if (a == 0) { + return (MathError.NO_ERROR, 0); + } + + uint c = a * b; + + if (c / a != b) { + return (MathError.INTEGER_OVERFLOW, 0); + } else { + return (MathError.NO_ERROR, c); + } + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function divUInt(uint a, uint b) internal pure returns (MathError, uint) { + if (b == 0) { + return (MathError.DIVISION_BY_ZERO, 0); + } + + return (MathError.NO_ERROR, a / b); + } + + /** + * @dev Subtracts two numbers, returns an error on overflow (i.e. if subtrahend is greater than minuend). + */ + function subUInt(uint a, uint b) internal pure returns (MathError, uint) { + if (b <= a) { + return (MathError.NO_ERROR, a - b); + } else { + return (MathError.INTEGER_UNDERFLOW, 0); + } + } + + /** + * @dev Adds two numbers, returns an error on overflow. + */ + function addUInt(uint a, uint b) internal pure returns (MathError, uint) { + uint c = a + b; + + if (c >= a) { + return (MathError.NO_ERROR, c); + } else { + return (MathError.INTEGER_OVERFLOW, 0); + } + } + + /** + * @dev add a and b and then subtract c + */ + function addThenSubUInt(uint a, uint b, uint c) internal pure returns (MathError, uint) { + (MathError err0, uint sum) = addUInt(a, b); + + if (err0 != MathError.NO_ERROR) { + return (err0, 0); + } + + return subUInt(sum, c); + } +} + + +/** + * @title Exponential module for storing fixed-precision decimals + * @author Compound + * @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places. + * Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is: + * `Exp({mantissa: 5100000000000000000})`. + */ +contract ExponentialNoError { + uint constant expScale = 1e18; + uint constant doubleScale = 1e36; + uint constant halfExpScale = expScale/2; + uint constant mantissaOne = expScale; + + struct Exp { + uint mantissa; + } + + struct Double { + uint mantissa; + } + + /** + * @dev Truncates the given exp to a whole number value. + * For example, truncate(Exp{mantissa: 15 * expScale}) = 15 + */ + function truncate(Exp memory exp) pure internal returns (uint) { + // Note: We are not using careful Math here as we're performing a division that cannot fail + return exp.mantissa / expScale; + } + + /** + * @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer. + */ + function mul_ScalarTruncate(Exp memory a, uint scalar) pure internal returns (uint) { + Exp memory product = mul_(a, scalar); + return truncate(product); + } + + /** + * @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer. + */ + function mul_ScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) pure internal returns (uint) { + Exp memory product = mul_(a, scalar); + return add_(truncate(product), addend); + } + + /** + * @dev Checks if first Exp is less than second Exp. + */ + function lessThanExp(Exp memory left, Exp memory right) pure internal returns (bool) { + return left.mantissa < right.mantissa; + } + + /** + * @dev Checks if left Exp <= right Exp. + */ + function lessThanOrEqualExp(Exp memory left, Exp memory right) pure internal returns (bool) { + return left.mantissa <= right.mantissa; + } + + /** + * @dev Checks if left Exp > right Exp. + */ + function greaterThanExp(Exp memory left, Exp memory right) pure internal returns (bool) { + return left.mantissa > right.mantissa; + } + + /** + * @dev returns true if Exp is exactly zero + */ + function isZeroExp(Exp memory value) pure internal returns (bool) { + return value.mantissa == 0; + } + + function safe224(uint n, string memory errorMessage) pure internal returns (uint224) { + require(n < 2**224, errorMessage); + return uint224(n); + } + + function safe32(uint n, string memory errorMessage) pure internal returns (uint32) { + require(n < 2**32, errorMessage); + return uint32(n); + } + + function add_(Exp memory a, Exp memory b) pure internal returns (Exp memory) { + return Exp({mantissa: add_(a.mantissa, b.mantissa)}); + } + + function add_(Double memory a, Double memory b) pure internal returns (Double memory) { + return Double({mantissa: add_(a.mantissa, b.mantissa)}); + } + + function add_(uint a, uint b) pure internal returns (uint) { + return add_(a, b, "addition overflow"); + } + + function add_(uint a, uint b, string memory errorMessage) pure internal returns (uint) { + uint c = a + b; + require(c >= a, errorMessage); + return c; + } + + function sub_(Exp memory a, Exp memory b) pure internal returns (Exp memory) { + return Exp({mantissa: sub_(a.mantissa, b.mantissa)}); + } + + function sub_(Double memory a, Double memory b) pure internal returns (Double memory) { + return Double({mantissa: sub_(a.mantissa, b.mantissa)}); + } + + function sub_(uint a, uint b) pure internal returns (uint) { + return sub_(a, b, "subtraction underflow"); + } + + function sub_(uint a, uint b, string memory errorMessage) pure internal returns (uint) { + require(b <= a, errorMessage); + return a - b; + } + + function mul_(Exp memory a, Exp memory b) pure internal returns (Exp memory) { + return Exp({mantissa: mul_(a.mantissa, b.mantissa) / expScale}); + } + + function mul_(Exp memory a, uint b) pure internal returns (Exp memory) { + return Exp({mantissa: mul_(a.mantissa, b)}); + } + + function mul_(uint a, Exp memory b) pure internal returns (uint) { + return mul_(a, b.mantissa) / expScale; + } + + function mul_(Double memory a, Double memory b) pure internal returns (Double memory) { + return Double({mantissa: mul_(a.mantissa, b.mantissa) / doubleScale}); + } + + function mul_(Double memory a, uint b) pure internal returns (Double memory) { + return Double({mantissa: mul_(a.mantissa, b)}); + } + + function mul_(uint a, Double memory b) pure internal returns (uint) { + return mul_(a, b.mantissa) / doubleScale; + } + + function mul_(uint a, uint b) pure internal returns (uint) { + return mul_(a, b, "multiplication overflow"); + } + + function mul_(uint a, uint b, string memory errorMessage) pure internal returns (uint) { + if (a == 0 || b == 0) { + return 0; + } + uint c = a * b; + require(c / a == b, errorMessage); + return c; + } + + function div_(Exp memory a, Exp memory b) pure internal returns (Exp memory) { + return Exp({mantissa: div_(mul_(a.mantissa, expScale), b.mantissa)}); + } + + function div_(Exp memory a, uint b) pure internal returns (Exp memory) { + return Exp({mantissa: div_(a.mantissa, b)}); + } + + function div_(uint a, Exp memory b) pure internal returns (uint) { + return div_(mul_(a, expScale), b.mantissa); + } + + function div_(Double memory a, Double memory b) pure internal returns (Double memory) { + return Double({mantissa: div_(mul_(a.mantissa, doubleScale), b.mantissa)}); + } + + function div_(Double memory a, uint b) pure internal returns (Double memory) { + return Double({mantissa: div_(a.mantissa, b)}); + } + + function div_(uint a, Double memory b) pure internal returns (uint) { + return div_(mul_(a, doubleScale), b.mantissa); + } + + function div_(uint a, uint b) pure internal returns (uint) { + return div_(a, b, "divide by zero"); + } + + function div_(uint a, uint b, string memory errorMessage) pure internal returns (uint) { + require(b > 0, errorMessage); + return a / b; + } + + function fraction(uint a, uint b) pure internal returns (Double memory) { + return Double({mantissa: div_(mul_(a, doubleScale), b)}); + } +} + + +/** + * @title Exponential module for storing fixed-precision decimals + * @author Compound + * @dev Legacy contract for compatibility reasons with existing contracts that still use MathError + * @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places. + * Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is: + * `Exp({mantissa: 5100000000000000000})`. + */ +contract Exponential is CarefulMath, ExponentialNoError { + /** + * @dev Creates an exponential from numerator and denominator values. + * Note: Returns an error if (`num` * 10e18) > MAX_INT, + * or if `denom` is zero. + */ + function getExp(uint num, uint denom) pure internal returns (MathError, Exp memory) { + (MathError err0, uint scaledNumerator) = mulUInt(num, expScale); + if (err0 != MathError.NO_ERROR) { + return (err0, Exp({mantissa: 0})); + } + + (MathError err1, uint rational) = divUInt(scaledNumerator, denom); + if (err1 != MathError.NO_ERROR) { + return (err1, Exp({mantissa: 0})); + } + + return (MathError.NO_ERROR, Exp({mantissa: rational})); + } + + /** + * @dev Adds two exponentials, returning a new exponential. + */ + function addExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) { + (MathError error, uint result) = addUInt(a.mantissa, b.mantissa); + + return (error, Exp({mantissa: result})); + } + + /** + * @dev Subtracts two exponentials, returning a new exponential. + */ + function subExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) { + (MathError error, uint result) = subUInt(a.mantissa, b.mantissa); + + return (error, Exp({mantissa: result})); + } + + /** + * @dev Multiply an Exp by a scalar, returning a new Exp. + */ + function mulScalar(Exp memory a, uint scalar) pure internal returns (MathError, Exp memory) { + (MathError err0, uint scaledMantissa) = mulUInt(a.mantissa, scalar); + if (err0 != MathError.NO_ERROR) { + return (err0, Exp({mantissa: 0})); + } + + return (MathError.NO_ERROR, Exp({mantissa: scaledMantissa})); + } + + /** + * @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer. + */ + function mulScalarTruncate(Exp memory a, uint scalar) pure internal returns (MathError, uint) { + (MathError err, Exp memory product) = mulScalar(a, scalar); + if (err != MathError.NO_ERROR) { + return (err, 0); + } + + return (MathError.NO_ERROR, truncate(product)); + } + + /** + * @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer. + */ + function mulScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) pure internal returns (MathError, uint) { + (MathError err, Exp memory product) = mulScalar(a, scalar); + if (err != MathError.NO_ERROR) { + return (err, 0); + } + + return addUInt(truncate(product), addend); + } + + /** + * @dev Divide an Exp by a scalar, returning a new Exp. + */ + function divScalar(Exp memory a, uint scalar) pure internal returns (MathError, Exp memory) { + (MathError err0, uint descaledMantissa) = divUInt(a.mantissa, scalar); + if (err0 != MathError.NO_ERROR) { + return (err0, Exp({mantissa: 0})); + } + + return (MathError.NO_ERROR, Exp({mantissa: descaledMantissa})); + } + + /** + * @dev Divide a scalar by an Exp, returning a new Exp. + */ + function divScalarByExp(uint scalar, Exp memory divisor) pure internal returns (MathError, Exp memory) { + /* + We are doing this as: + getExp(mulUInt(expScale, scalar), divisor.mantissa) + + How it works: + Exp = a / b; + Scalar = s; + `s / (a / b)` = `b * s / a` and since for an Exp `a = mantissa, b = expScale` + */ + (MathError err0, uint numerator) = mulUInt(expScale, scalar); + if (err0 != MathError.NO_ERROR) { + return (err0, Exp({mantissa: 0})); + } + return getExp(numerator, divisor.mantissa); + } + + /** + * @dev Divide a scalar by an Exp, then truncate to return an unsigned integer. + */ + function divScalarByExpTruncate(uint scalar, Exp memory divisor) pure internal returns (MathError, uint) { + (MathError err, Exp memory fraction) = divScalarByExp(scalar, divisor); + if (err != MathError.NO_ERROR) { + return (err, 0); + } + + return (MathError.NO_ERROR, truncate(fraction)); + } + + /** + * @dev Multiplies two exponentials, returning a new exponential. + */ + function mulExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) { + + (MathError err0, uint doubleScaledProduct) = mulUInt(a.mantissa, b.mantissa); + if (err0 != MathError.NO_ERROR) { + return (err0, Exp({mantissa: 0})); + } + + // We add half the scale before dividing so that we get rounding instead of truncation. + // See "Listing 6" and text above it at https://accu.org/index.php/journals/1717 + // Without this change, a result like 6.6...e-19 will be truncated to 0 instead of being rounded to 1e-18. + (MathError err1, uint doubleScaledProductWithHalfScale) = addUInt(halfExpScale, doubleScaledProduct); + if (err1 != MathError.NO_ERROR) { + return (err1, Exp({mantissa: 0})); + } + + (MathError err2, uint product) = divUInt(doubleScaledProductWithHalfScale, expScale); + // The only error `div` can return is MathError.DIVISION_BY_ZERO but we control `expScale` and it is not zero. + assert(err2 == MathError.NO_ERROR); + + return (MathError.NO_ERROR, Exp({mantissa: product})); + } + + /** + * @dev Multiplies two exponentials given their mantissas, returning a new exponential. + */ + function mulExp(uint a, uint b) pure internal returns (MathError, Exp memory) { + return mulExp(Exp({mantissa: a}), Exp({mantissa: b})); + } + + /** + * @dev Multiplies three exponentials, returning a new exponential. + */ + function mulExp3(Exp memory a, Exp memory b, Exp memory c) pure internal returns (MathError, Exp memory) { + (MathError err, Exp memory ab) = mulExp(a, b); + if (err != MathError.NO_ERROR) { + return (err, ab); + } + return mulExp(ab, c); + } + + /** + * @dev Divides two exponentials, returning a new exponential. + * (a/scale) / (b/scale) = (a/scale) * (scale/b) = a/b, + * which we can scale as an Exp by calling getExp(a.mantissa, b.mantissa) + */ + function divExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) { + return getExp(a.mantissa, b.mantissa); + } +} + + + +/** + * @title ERC 20 Token Standard Interface + * https://eips.ethereum.org/EIPS/eip-20 + */ +interface EIP20Interface { + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + + /** + * @notice Get the total number of tokens in circulation + * @return The supply of tokens + */ + function totalSupply() external view returns (uint256); + + /** + * @notice Gets the balance of the specified address + * @param owner The address from which the balance will be retrieved + * @return The balance + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @notice Transfer `amount` tokens from `msg.sender` to `dst` + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transfer(address dst, uint256 amount) external returns (bool success); + + /** + * @notice Transfer `amount` tokens from `src` to `dst` + * @param src The address of the source account + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transferFrom(address src, address dst, uint256 amount) external returns (bool success); + + /** + * @notice Approve `spender` to transfer up to `amount` from `src` + * @dev This will overwrite the approval amount for `spender` + * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) + * @param spender The address of the account which may transfer tokens + * @param amount The number of tokens that are approved (-1 means infinite) + * @return Whether or not the approval succeeded + */ + function approve(address spender, uint256 amount) external returns (bool success); + + /** + * @notice Get the current allowance from `owner` for `spender` + * @param owner The address of the account which owns the tokens to be spent + * @param spender The address of the account which may transfer tokens + * @return The number of tokens allowed to be spent (-1 means infinite) + */ + function allowance(address owner, address spender) external view returns (uint256 remaining); + + event Transfer(address indexed from, address indexed to, uint256 amount); + event Approval(address indexed owner, address indexed spender, uint256 amount); +} + + + +interface RegistryForOToken { + function isSupportedInterestRateModel(address interestRateModel) external returns (bool); + function olaBankAddress() external view returns (address payable); + function blocksBased() external view returns (bool); +} + +interface ComptrollerForOToken { + function adminBankAddress() external view returns (address payable); +} + +/** + * View functions that are not used by the core contracts. + */ +contract CTokenViewInterface { + /*** View Interface ***/ + function borrowRatePerBlock() external view returns (uint); + function supplyRatePerBlock() external view returns (uint); + function totalBorrowsCurrent() external returns (uint); + + /** + * @notice Used by the Maximilion + */ + function borrowBalanceCurrent(address account) external returns (uint); + function exchangeRateCurrent() public returns (uint); + function getCash() external view returns (uint); +} + + +contract CTokenInterface { + // OLA_ADDITIONS : "Underlying field" + address constant public nativeCoinUnderlying = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); + + /** + * OLA_ADDITIONS : This field + * @notice This value is hard coded to 0.5 (50% for the Ola ecosystem and the LeN owner each) + */ + uint constant public olaReserveFactorMantissa = 0.5e18; + + /** + * @notice Indicator that this is a CToken contract (for inspection) + */ + bool public constant isCToken = true; + + /** + * @notice Maximum borrow rate that can ever be applied (.0005% / block) + */ + uint internal constant borrowRateMaxMantissa = 0.0005e16; + + /** + * @notice Maximum fraction of interest that can be set aside for reserves + */ + uint internal constant reserveFactorMaxMantissa = 0.3e18; + + /** + * OLA_ADDITIONS : This value + * @notice Minimum fraction of interest that can be set aside for reserves + */ + uint internal constant reserveFactorMinMantissa = 0.05e18; + + /*** Market Events ***/ + + /** + * @notice Event emitted when interest is accrued + */ + event AccrueInterest(uint cashPrior, uint interestAccumulated, uint borrowIndex, uint totalBorrows); + + /** + * @notice Event emitted when tokens are minted + */ + event Mint(address minter, uint mintAmount, uint mintTokens); + + /** + * @notice Event emitted when tokens are redeemed + */ + event Redeem(address redeemer, uint redeemAmount, uint redeemTokens); + + /** + * @notice Event emitted when underlying is borrowed + */ + event Borrow(address borrower, uint borrowAmount, uint accountBorrows, uint totalBorrows); + + /** + * @notice Event emitted when a borrow is repaid + */ + event RepayBorrow(address payer, address borrower, uint repayAmount, uint accountBorrows, uint totalBorrows); + + /** + * @notice Event emitted when a borrow is liquidated + */ + event LiquidateBorrow(address liquidator, address borrower, uint repayAmount, address cTokenCollateral, uint seizeTokens); + + + /*** Admin Events ***/ + + /** + * @notice Event emitted when pendingAdmin is changed + */ + event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin); + + /** + * @notice Event emitted when pendingAdmin is accepted, which means admin is updated + */ + event NewAdmin(address oldAdmin, address newAdmin); + + /** + * @notice Event emitted when Comptroller is changed + */ + event NewComptroller(ComptrollerInterface oldComptroller, ComptrollerInterface newComptroller); + + /** + * @notice Event emitted when interestRateModel is changed + */ + event NewMarketInterestRateModel(InterestRateModel oldInterestRateModel, InterestRateModel newInterestRateModel); + + /** + * @notice Event emitted when the reserve factor is changed + */ + event NewReserveFactor(uint oldReserveFactorMantissa, uint newReserveFactorMantissa); + + /** + * @notice Event emitted when the reserves are reduced + */ + event ReservesReduced(address admin, uint adminPart, address olaBank, uint olaPart, uint newTotalReserves); + + /** + * @notice EIP20 Transfer event + */ + event Transfer(address indexed from, address indexed to, uint amount); + + /** + * @notice EIP20 Approval event + */ + event Approval(address indexed owner, address indexed spender, uint amount); + + /** + * @notice Failure event + */ + event Failure(uint error, uint info, uint detail); + + /*** User Interface ***/ + + function transfer(address dst, uint amount) external returns (bool); + function transferFrom(address src, address dst, uint amount) external returns (bool); + function approve(address spender, uint amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint); + function balanceOf(address owner) external view returns (uint); + function balanceOfUnderlying(address owner) external returns (uint); + function getAccountSnapshot(address account) external view returns (uint, uint, uint, uint); + function borrowBalanceStored(address account) public view returns (uint); + function exchangeRateStored() public view returns (uint); + function getAccrualBlockNumber() external view returns (uint); + function accrueInterest() public returns (uint); + function seize(address liquidator, address borrower, uint seizeTokens) external returns (uint); + + /*** Admin Functions ***/ + + function _setPendingAdmin(address payable newPendingAdmin) external returns (uint); + function _acceptAdmin() external returns (uint); + function _setReserveFactor(uint newReserveFactorMantissa) external returns (uint); + function _reduceReserves(uint reduceAmount) external returns (uint); + function _setInterestRateModel(InterestRateModel newInterestRateModel) public returns (uint); +} + + +/** + * OLA_ADDITIONS : This base admin storage. + */ +contract CTokenAdminStorage { + /** + * @notice Administrator for this contract + */ + address payable public admin; + + /** + * @notice Pending administrator for this contract + */ + address payable public pendingAdmin; + + /** + * @notice Contract which oversees inter-cToken operations + */ + ComptrollerInterface public comptroller; + + /** + * @notice Implementation address for this contract + */ + address public implementation; + + // OLA_ADDITIONS : Contract hash name + bytes32 public contractNameHash; +} + +/** + * @notice DO NOT ADD ANY MORE STORAGE VARIABLES HERE (add them to their respective type storage) + */ +contract CTokenStorage is CTokenAdminStorage { + /** + * @dev Guard variable for re-entrancy checks + */ + bool internal _notEntered; + + /** + * @notice EIP-20 token name for this token + */ + string public name; + + /** + * @notice EIP-20 token symbol for this token + */ + string public symbol; + + /** + * @notice EIP-20 token decimals for this token + */ + uint8 public decimals; + + /** + * @notice Underlying asset for this CToken + */ + address public underlying; + + // @notice Indicates if the calculations should be blocks or time based + bool public blocksBased; + + /** + * @notice Model which tells what the current interest rate should be + */ + InterestRateModel public interestRateModel; + + /** + * @notice Initial exchange rate used when minting the first CTokens (used when totalSupply = 0) + */ + uint internal initialExchangeRateMantissa; + + /** + * @notice Fraction of interest currently set aside for reserves + */ + uint public reserveFactorMantissa; + + /** + * @notice Block number that interest was last accrued at + */ + uint public accrualBlockNumber; + + /** + * @notice Block number that interest was last accrued at + */ + uint public accrualBlockTimestamp; + + /** + * @notice Accumulator of the total earned interest rate since the opening of the market + */ + uint public borrowIndex; + + /** + * @notice Total amount of outstanding borrows of the underlying in this market + */ + uint public totalBorrows; + + /** + * OLA_ADDITIONS : Removed option to 'add reserves' as it makes no sense when reducing reserves + * sends a part to Ola Bank. + * @notice Total amount of reserves of the underlying held in this market + */ + uint public totalReserves; + + /** + * @notice Total number of tokens in circulation + */ + uint public totalSupply; + + /** + * @notice Official record of token balances for each account + */ + mapping (address => uint) internal accountTokens; + + /** + * @notice Approved token transfer amounts on behalf of others + */ + mapping (address => mapping (address => uint)) internal transferAllowances; + + /** + * @notice Container for borrow balance information + * @member principal Total balance (with accrued interest), after applying the most recent balance-changing action + * @member interestIndex Global borrowIndex as of the most recent balance-changing action + */ + struct BorrowSnapshot { + uint principal; + uint interestIndex; + } + + /** + * @notice Mapping of account addresses to outstanding borrow balances + */ + mapping(address => BorrowSnapshot) internal accountBorrows; + + // IMPORTANT : DO NOT ADD ANY MORE STORAGE VARIABLES HERE (add them to their respective type storage) +} + +/** + * @title Compound's CToken Contract + * @notice Abstract base for CTokens + * @author Compound + */ +contract CToken is CTokenStorage, CTokenInterface, CTokenViewInterface, Exponential, TokenErrorReporter { + /** + * @notice Initialize the money market + * @param comptroller_ The address of the Comptroller + * @param interestRateModel_ The address of the interest rate model + * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18 + * @param name_ EIP-20 name of this token + * @param symbol_ EIP-20 symbol of this token + * @param decimals_ EIP-20 decimal precision of this token + */ + function initialize(ComptrollerInterface comptroller_, + InterestRateModel interestRateModel_, + uint initialExchangeRateMantissa_, + string memory name_, + string memory symbol_, + uint8 decimals_) public { + require(msg.sender == admin, "only admin may initialize the market"); + require(accrualBlockNumber == 0 && borrowIndex == 0, "market may only be initialized once"); + + // Set initial exchange rate + initialExchangeRateMantissa = initialExchangeRateMantissa_; + require(initialExchangeRateMantissa > 0, "initial exchange rate must be greater than zero."); + + // Set the Comptroller + uint err = _setComptroller(comptroller_); + require(err == uint(Error.NO_ERROR), "setting comptroller failed"); + + // Initialize block number and borrow index (block number mocks depend on Comptroller being set) + accrualBlockNumber = getBlockNumber(); + accrualBlockTimestamp = getBlockTimestamp(); + borrowIndex = mantissaOne; + + // Set the calculation based flag from the ministry + RegistryForOToken ministry = RegistryForOToken(comptroller.getRegistry()); + blocksBased = ministry.blocksBased(); + + // Set the interest rate model (depends on block number / borrow index) + err = _setInterestRateModelFresh(interestRateModel_); + require(err == uint(Error.NO_ERROR), "setting interest rate model failed"); + + name = name_; + symbol = symbol_; + decimals = decimals_; + + // The counter starts true to prevent changing it from zero to non-zero (i.e. smaller cost/refund) + _notEntered = true; + } + + /** + * @notice Transfer `tokens` tokens from `src` to `dst` by `spender` + * @dev Called by both `transfer` and `transferFrom` internally + * @param spender The address of the account performing the transfer + * @param src The address of the source account + * @param dst The address of the destination account + * @param tokens The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transferTokens(address spender, address src, address dst, uint tokens) internal returns (uint) { + /* Fail if transfer not allowed */ + uint allowed = comptroller.transferAllowed(address(this), src, dst, tokens); + if (allowed != 0) { + return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.TRANSFER_COMPTROLLER_REJECTION, allowed); + } + + /* Do not allow self-transfers */ + if (src == dst) { + return fail(Error.BAD_INPUT, FailureInfo.TRANSFER_NOT_ALLOWED); + } + + /* Get the allowance, infinite for the account owner */ + uint startingAllowance = 0; + if (spender == src) { + startingAllowance = uint(-1); + } else { + startingAllowance = transferAllowances[src][spender]; + } + + /* Do the calculations, checking for {under,over}flow */ + MathError mathErr; + uint allowanceNew; + uint srcTokensNew; + uint dstTokensNew; + + (mathErr, allowanceNew) = subUInt(startingAllowance, tokens); + if (mathErr != MathError.NO_ERROR) { + return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ALLOWED); + } + + (mathErr, srcTokensNew) = subUInt(accountTokens[src], tokens); + if (mathErr != MathError.NO_ERROR) { + return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ENOUGH); + } + + (mathErr, dstTokensNew) = addUInt(accountTokens[dst], tokens); + if (mathErr != MathError.NO_ERROR) { + return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_TOO_MUCH); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + accountTokens[src] = srcTokensNew; + accountTokens[dst] = dstTokensNew; + + /* Eat some of the allowance (if necessary) */ + if (startingAllowance != uint(-1)) { + transferAllowances[src][spender] = allowanceNew; + } + + /* We emit a Transfer event */ + emit Transfer(src, dst, tokens); + + // unused function + comptroller.transferVerify(address(this), src, dst, tokens); + + return uint(Error.NO_ERROR); + } + + /** + * @notice Transfer `amount` tokens from `msg.sender` to `dst` + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transfer(address dst, uint256 amount) external nonReentrant returns (bool) { + return transferTokens(msg.sender, msg.sender, dst, amount) == uint(Error.NO_ERROR); + } + + /** + * @notice Transfer `amount` tokens from `src` to `dst` + * @param src The address of the source account + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transferFrom(address src, address dst, uint256 amount) external nonReentrant returns (bool) { + return transferTokens(msg.sender, src, dst, amount) == uint(Error.NO_ERROR); + } + + /** + * @notice Approve `spender` to transfer up to `amount` from `src` + * @dev This will overwrite the approval amount for `spender` + * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) + * @param spender The address of the account which may transfer tokens + * @param amount The number of tokens that are approved (-1 means infinite) + * @return Whether or not the approval succeeded + */ + function approve(address spender, uint256 amount) external returns (bool) { + address src = msg.sender; + transferAllowances[src][spender] = amount; + emit Approval(src, spender, amount); + return true; + } + + /** + * @notice Get the current allowance from `owner` for `spender` + * @param owner The address of the account which owns the tokens to be spent + * @param spender The address of the account which may transfer tokens + * @return The number of tokens allowed to be spent (-1 means infinite) + */ + function allowance(address owner, address spender) external view returns (uint256) { + return transferAllowances[owner][spender]; + } + + /** + * @notice Get the token balance of the `owner` + * @param owner The address of the account to query + * @return The number of tokens owned by `owner` + */ + function balanceOf(address owner) external view returns (uint256) { + return accountTokens[owner]; + } + + /** + * @notice Get the underlying balance of the `owner` + * @dev This also accrues interest in a transaction + * @param owner The address of the account to query + * @return The amount of underlying owned by `owner` + */ + function balanceOfUnderlying(address owner) external returns (uint) { + Exp memory exchangeRate = Exp({mantissa: exchangeRateCurrent()}); + (MathError mErr, uint balance) = mulScalarTruncate(exchangeRate, accountTokens[owner]); + require(mErr == MathError.NO_ERROR, "balance could not be calculated"); + return balance; + } + + /** + * @notice Get a snapshot of the account's balances, and the cached exchange rate + * @dev This is used by Comptroller to more efficiently perform liquidity checks. + * @param account Address of the account to snapshot + * @return (possible error, token balance, borrow balance, exchange rate mantissa) + */ + function getAccountSnapshot(address account) external view returns (uint, uint, uint, uint) { + uint cTokenBalance = accountTokens[account]; + uint borrowBalance; + uint exchangeRateMantissa; + + MathError mErr; + + (mErr, borrowBalance) = borrowBalanceStoredInternal(account); + if (mErr != MathError.NO_ERROR) { + return (uint(Error.MATH_ERROR), 0, 0, 0); + } + + (mErr, exchangeRateMantissa) = exchangeRateStoredInternal(); + if (mErr != MathError.NO_ERROR) { + return (uint(Error.MATH_ERROR), 0, 0, 0); + } + + return (uint(Error.NO_ERROR), cTokenBalance, borrowBalance, exchangeRateMantissa); + } + + /** + * @dev Function to simply retrieve block number + * This exists mainly for inheriting test contracts to stub this result. + */ + function getBlockNumber() internal view returns (uint) { + return block.number; + } + + /** + * @dev Function to simply retrieve block timestamp + * This exists mainly for inheriting test contracts to stub this result. + */ + function getBlockTimestamp() internal view returns (uint) { + return block.timestamp; + } + + /** + * @notice Returns the current per-block borrow interest rate for this cToken + * @return The borrow interest rate per block, scaled by 1e18 + */ + function borrowRatePerBlock() external view returns (uint) { + return interestRateModel.getBorrowRate(getCashPrior(), totalBorrows, totalReserves); + } + + /** + * @notice Returns the current per-block supply interest rate for this cToken + * @return The supply interest rate per block, scaled by 1e18 + */ + function supplyRatePerBlock() external view returns (uint) { + return interestRateModel.getSupplyRate(getCashPrior(), totalBorrows, totalReserves, reserveFactorMantissa); + } + + /** + * @notice Returns the current total borrows plus accrued interest + * @return The total borrows with interest + */ + function totalBorrowsCurrent() external nonReentrant returns (uint) { + require(accrueInterest() == uint(Error.NO_ERROR), "accrue interest failed"); + return totalBorrows; + } + + /** + * @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex + * @param account The address whose balance should be calculated after updating borrowIndex + * @return The calculated balance + */ + function borrowBalanceCurrent(address account) external nonReentrant returns (uint) { + require(accrueInterest() == uint(Error.NO_ERROR), "accrue interest failed"); + return borrowBalanceStored(account); + } + + /** + * @notice Return the borrow balance of account based on stored data + * @param account The address whose balance should be calculated + * @return The calculated balance + */ + function borrowBalanceStored(address account) public view returns (uint) { + (MathError err, uint result) = borrowBalanceStoredInternal(account); + require(err == MathError.NO_ERROR, "borrowBalanceStored: borrowBalanceStoredInternal failed"); + return result; + } + + /** + * @notice Return the borrow balance of account based on stored data + * @param account The address whose balance should be calculated + * @return (error code, the calculated balance or 0 if error code is non-zero) + */ + function borrowBalanceStoredInternal(address account) internal view returns (MathError, uint) { + /* Note: we do not assert that the market is up to date */ + MathError mathErr; + uint principalTimesIndex; + uint result; + + /* Get borrowBalance and borrowIndex */ + BorrowSnapshot storage borrowSnapshot = accountBorrows[account]; + + /* If borrowBalance = 0 then borrowIndex is likely also 0. + * Rather than failing the calculation with a division by 0, we immediately return 0 in this case. + */ + if (borrowSnapshot.principal == 0) { + return (MathError.NO_ERROR, 0); + } + + /* Calculate new borrow balance using the interest index: + * recentBorrowBalance = borrower.borrowBalance * market.borrowIndex / borrower.borrowIndex + */ + (mathErr, principalTimesIndex) = mulUInt(borrowSnapshot.principal, borrowIndex); + if (mathErr != MathError.NO_ERROR) { + return (mathErr, 0); + } + + (mathErr, result) = divUInt(principalTimesIndex, borrowSnapshot.interestIndex); + if (mathErr != MathError.NO_ERROR) { + return (mathErr, 0); + } + + return (MathError.NO_ERROR, result); + } + + /** + * @notice Accrue interest then return the up-to-date exchange rate + * @return Calculated exchange rate scaled by 1e18 + */ + function exchangeRateCurrent() public nonReentrant returns (uint) { + require(accrueInterest() == uint(Error.NO_ERROR), "accrue interest failed"); + return exchangeRateStored(); + } + + /** + * @notice Calculates the exchange rate from the underlying to the CToken + * @dev This function does not accrue interest before calculating the exchange rate + * @return Calculated exchange rate scaled by 1e18 + */ + function exchangeRateStored() public view returns (uint) { + (MathError err, uint result) = exchangeRateStoredInternal(); + require(err == MathError.NO_ERROR, "exchangeRateStored: exchangeRateStoredInternal failed"); + return result; + } + + /** + * @notice Calculates the exchange rate from the underlying to the CToken + * @dev This function does not accrue interest before calculating the exchange rate + * @return (error code, calculated exchange rate scaled by 1e18) + */ + function exchangeRateStoredInternal() internal view returns (MathError, uint) { + uint _totalSupply = totalSupply; + if (_totalSupply == 0) { + /* + * If there are no tokens minted: + * exchangeRate = initialExchangeRate + */ + return (MathError.NO_ERROR, initialExchangeRateMantissa); + } else { + /* + * Otherwise: + * exchangeRate = (totalCash + totalBorrows - totalReserves) / totalSupply + */ + uint totalCash = getCashPrior(); + uint cashPlusBorrowsMinusReserves; + Exp memory exchangeRate; + MathError mathErr; + + (mathErr, cashPlusBorrowsMinusReserves) = addThenSubUInt(totalCash, totalBorrows, totalReserves); + if (mathErr != MathError.NO_ERROR) { + return (mathErr, 0); + } + + (mathErr, exchangeRate) = getExp(cashPlusBorrowsMinusReserves, _totalSupply); + if (mathErr != MathError.NO_ERROR) { + return (mathErr, 0); + } + + return (MathError.NO_ERROR, exchangeRate.mantissa); + } + } + + /** + * @notice Get cash balance of this cToken in the underlying asset + * @return The quantity of underlying asset owned by this contract + */ + function getCash() external view returns (uint) { + return getCashPrior(); + } + + /** + * @notice Get the accrual block number of this cToken + * @return The accrual block number + */ + function getAccrualBlockNumber() external view returns (uint) { + return accrualBlockNumber; + } + + /** + * @notice Applies accrued interest to total borrows and reserves + * @dev This calculates interest accrued from the last checkpointed block + * up to the current block and writes new checkpoint to storage. + */ + function accrueInterest() public returns (uint) { + /* Remember the initial block number */ + uint currentBlockNumber = getBlockNumber(); + uint accrualBlockNumberPrior = accrualBlockNumber; + uint currentBlockTimestamp = getBlockTimestamp(); + + /* Short-circuit accumulating 0 interest */ + if (accrualBlockNumberPrior == currentBlockNumber) { + return uint(Error.NO_ERROR); + } + + // OLA_ADDITIONS : Distinction between time and block based calculations + /* Calculate the number of blocks elapsed since the last accrual */ + MathError mathErr; + uint delta; + + if (blocksBased) { + (mathErr, delta) = subUInt(currentBlockNumber, accrualBlockNumberPrior); + } else { + // This variable is defined here due to solidity limits + uint accrualBlockTimestampPrior = accrualBlockTimestamp; + + /* Short-circuit accumulating 0 interest on time based chains + extra safety for weird timestamps */ + if (currentBlockTimestamp <= accrualBlockTimestampPrior) { + return uint(Error.NO_ERROR); + } + + (mathErr, delta) = subUInt(currentBlockTimestamp, accrualBlockTimestampPrior); + } + require(mathErr == MathError.NO_ERROR, "could not calculate delta"); + + /* Read the previous values out of storage */ + uint cashPrior = getCashPrior(); + uint borrowsPrior = totalBorrows; + uint reservesPrior = totalReserves; + uint borrowIndexPrior = borrowIndex; + + /* Calculate the current borrow interest rate */ + uint borrowRateMantissa = interestRateModel.getBorrowRate(cashPrior, borrowsPrior, reservesPrior); + require(borrowRateMantissa <= borrowRateMaxMantissa, "borrow rate is absurdly high"); + + /* + * Calculate the interest accumulated into borrows and reserves and the new index: + * simpleInterestFactor = borrowRate * delta + * interestAccumulated = simpleInterestFactor * totalBorrows + * totalBorrowsNew = interestAccumulated + totalBorrows + * totalReservesNew = interestAccumulated * reserveFactor + totalReserves + * borrowIndexNew = simpleInterestFactor * borrowIndex + borrowIndex + */ + + Exp memory simpleInterestFactor; + uint interestAccumulated; + uint totalBorrowsNew; + uint totalReservesNew; + uint borrowIndexNew; + + (mathErr, simpleInterestFactor) = mulScalar(Exp({mantissa: borrowRateMantissa}), delta); + if (mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED, uint(mathErr)); + } + + (mathErr, interestAccumulated) = mulScalarTruncate(simpleInterestFactor, borrowsPrior); + if (mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED, uint(mathErr)); + } + + (mathErr, totalBorrowsNew) = addUInt(interestAccumulated, borrowsPrior); + if (mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED, uint(mathErr)); + } + + (mathErr, totalReservesNew) = mulScalarTruncateAddUInt(Exp({mantissa: reserveFactorMantissa}), interestAccumulated, reservesPrior); + if (mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED, uint(mathErr)); + } + + (mathErr, borrowIndexNew) = mulScalarTruncateAddUInt(simpleInterestFactor, borrowIndexPrior, borrowIndexPrior); + if (mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED, uint(mathErr)); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* We write the previously calculated values into storage */ + accrualBlockNumber = currentBlockNumber; + accrualBlockTimestamp = currentBlockTimestamp; + borrowIndex = borrowIndexNew; + totalBorrows = totalBorrowsNew; + totalReserves = totalReservesNew; + + /* We emit an AccrueInterest event */ + emit AccrueInterest(cashPrior, interestAccumulated, borrowIndexNew, totalBorrowsNew); + + return uint(Error.NO_ERROR); + } + + /** + * @notice Sender supplies assets into the market and receives cTokens in exchange + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param mintAmount The amount of the underlying asset to supply + * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount. + */ + function mintInternal(uint mintAmount) internal nonReentrant returns (uint, uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed + return (fail(Error(error), FailureInfo.MINT_ACCRUE_INTEREST_FAILED), 0); + } + // mintFresh emits the actual Mint event if successful and logs on errors, so we don't need to + return mintFresh(msg.sender, mintAmount); + } + + struct MintLocalVars { + Error err; + MathError mathErr; + uint exchangeRateMantissa; + uint mintTokens; + uint totalSupplyNew; + uint accountTokensNew; + uint actualMintAmount; + } + + /** + * @notice User supplies assets into the market and receives cTokens in exchange + * @dev Assumes interest has already been accrued up to the current block + * @param minter The address of the account which is supplying the assets + * @param mintAmount The amount of the underlying asset to supply + * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount. + */ + function mintFresh(address minter, uint mintAmount) internal returns (uint, uint) { + /* Fail if mint not allowed */ + uint allowed = comptroller.mintAllowed(address(this), minter, mintAmount); + if (allowed != 0) { + return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.MINT_COMPTROLLER_REJECTION, allowed), 0); + } + + /* Verify market's block number equals current block number */ + if (accrualBlockNumber != getBlockNumber()) { + return (fail(Error.MARKET_NOT_FRESH, FailureInfo.MINT_FRESHNESS_CHECK), 0); + } + + MintLocalVars memory vars; + + (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal(); + if (vars.mathErr != MathError.NO_ERROR) { + return (failOpaque(Error.MATH_ERROR, FailureInfo.MINT_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)), 0); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* + * We call `doTransferIn` for the minter and the mintAmount. + * Note: The cToken must handle variations between ERC-20 and ETH underlying. + * `doTransferIn` reverts if anything goes wrong, since we can't be sure if + * side-effects occurred. The function returns the amount actually transferred, + * in case of a fee. On success, the cToken holds an additional `actualMintAmount` + * of cash. + */ + vars.actualMintAmount = doTransferIn(minter, mintAmount); + + /* + * We get the current exchange rate and calculate the number of cTokens to be minted: + * mintTokens = actualMintAmount / exchangeRate + */ + + (vars.mathErr, vars.mintTokens) = divScalarByExpTruncate(vars.actualMintAmount, Exp({mantissa: vars.exchangeRateMantissa})); + require(vars.mathErr == MathError.NO_ERROR, "MINT_EXCHANGE_CALCULATION_FAILED"); + + /* + * We calculate the new total supply of cTokens and minter token balance, checking for overflow: + * totalSupplyNew = totalSupply + mintTokens + * accountTokensNew = accountTokens[minter] + mintTokens + */ + (vars.mathErr, vars.totalSupplyNew) = addUInt(totalSupply, vars.mintTokens); + require(vars.mathErr == MathError.NO_ERROR, "MINT_NEW_TOTAL_SUPPLY_CALCULATION_FAILED"); + + (vars.mathErr, vars.accountTokensNew) = addUInt(accountTokens[minter], vars.mintTokens); + require(vars.mathErr == MathError.NO_ERROR, "MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED"); + + /* We write previously calculated values into storage */ + totalSupply = vars.totalSupplyNew; + accountTokens[minter] = vars.accountTokensNew; + + /* We emit a Mint event, and a Transfer event */ + emit Mint(minter, vars.actualMintAmount, vars.mintTokens); + emit Transfer(address(this), minter, vars.mintTokens); + + /* We call the defense hook */ + // unused function + comptroller.mintVerify(address(this), minter, vars.actualMintAmount, vars.mintTokens); + + return (uint(Error.NO_ERROR), vars.actualMintAmount); + } + + /** + * @notice Sender redeems cTokens in exchange for the underlying asset + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param redeemTokens The number of cTokens to redeem into underlying + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function redeemInternal(uint redeemTokens) internal nonReentrant returns (uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed + return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED); + } + // redeemFresh emits redeem-specific logs on errors, so we don't need to + return redeemFresh(msg.sender, redeemTokens, 0); + } + + /** + * @notice Sender redeems cTokens in exchange for a specified amount of underlying asset + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param redeemAmount The amount of underlying to receive from redeeming cTokens + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function redeemUnderlyingInternal(uint redeemAmount) internal nonReentrant returns (uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed + return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED); + } + // redeemFresh emits redeem-specific logs on errors, so we don't need to + return redeemFresh(msg.sender, 0, redeemAmount); + } + + struct RedeemLocalVars { + Error err; + MathError mathErr; + uint exchangeRateMantissa; + uint redeemTokens; + uint redeemAmount; + uint totalSupplyNew; + uint accountTokensNew; + } + + /** + * @notice User redeems cTokens in exchange for the underlying asset + * @dev Assumes interest has already been accrued up to the current block + * @param redeemer The address of the account which is redeeming the tokens + * @param redeemTokensIn The number of cTokens to redeem into underlying (only one of redeemTokensIn or redeemAmountIn may be non-zero) + * @param redeemAmountIn The number of underlying tokens to receive from redeeming cTokens (only one of redeemTokensIn or redeemAmountIn may be non-zero) + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function redeemFresh(address payable redeemer, uint redeemTokensIn, uint redeemAmountIn) internal returns (uint) { + require(redeemTokensIn == 0 || redeemAmountIn == 0, "one of redeemTokensIn or redeemAmountIn must be zero"); + + RedeemLocalVars memory vars; + + /* exchangeRate = invoke Exchange Rate Stored() */ + (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal(); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)); + } + + /* If redeemTokensIn > 0: */ + if (redeemTokensIn > 0) { + /* + * We calculate the exchange rate and the amount of underlying to be redeemed: + * redeemTokens = redeemTokensIn + * redeemAmount = redeemTokensIn x exchangeRateCurrent + */ + vars.redeemTokens = redeemTokensIn; + + (vars.mathErr, vars.redeemAmount) = mulScalarTruncate(Exp({mantissa: vars.exchangeRateMantissa}), redeemTokensIn); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_TOKENS_CALCULATION_FAILED, uint(vars.mathErr)); + } + } else { + /* + * We get the current exchange rate and calculate the amount to be redeemed: + * redeemTokens = redeemAmountIn / exchangeRate + * redeemAmount = redeemAmountIn + */ + + (vars.mathErr, vars.redeemTokens) = divScalarByExpTruncate(redeemAmountIn, Exp({mantissa: vars.exchangeRateMantissa})); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_AMOUNT_CALCULATION_FAILED, uint(vars.mathErr)); + } + + vars.redeemAmount = redeemAmountIn; + } + + /* Fail if redeem not allowed */ + uint allowed = comptroller.redeemAllowed(address(this), redeemer, vars.redeemTokens); + if (allowed != 0) { + return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.REDEEM_COMPTROLLER_REJECTION, allowed); + } + + /* Verify market's block number equals current block number */ + if (accrualBlockNumber != getBlockNumber()) { + return fail(Error.MARKET_NOT_FRESH, FailureInfo.REDEEM_FRESHNESS_CHECK); + } + + /* + * We calculate the new total supply and redeemer balance, checking for underflow: + * totalSupplyNew = totalSupply - redeemTokens + * accountTokensNew = accountTokens[redeemer] - redeemTokens + */ + (vars.mathErr, vars.totalSupplyNew) = subUInt(totalSupply, vars.redeemTokens); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_NEW_TOTAL_SUPPLY_CALCULATION_FAILED, uint(vars.mathErr)); + } + + (vars.mathErr, vars.accountTokensNew) = subUInt(accountTokens[redeemer], vars.redeemTokens); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); + } + + /* Fail gracefully if protocol has insufficient cash */ + if (getCashPrior() < vars.redeemAmount) { + return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.REDEEM_TRANSFER_OUT_NOT_POSSIBLE); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* + * We invoke doTransferOut for the redeemer and the redeemAmount. + * Note: The cToken must handle variations between ERC-20 and ETH underlying. + * On success, the cToken has redeemAmount less of cash. + * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred. + */ + doTransferOut(redeemer, vars.redeemAmount); + + /* We write previously calculated values into storage */ + totalSupply = vars.totalSupplyNew; + accountTokens[redeemer] = vars.accountTokensNew; + + /* We emit a Transfer event, and a Redeem event */ + emit Transfer(redeemer, address(this), vars.redeemTokens); + emit Redeem(redeemer, vars.redeemAmount, vars.redeemTokens); + + /* We call the defense hook */ + comptroller.redeemVerify(address(this), redeemer, vars.redeemAmount, vars.redeemTokens); + + return uint(Error.NO_ERROR); + } + + /** + * @notice Sender borrows assets from the protocol to their own address + * @param borrowAmount The amount of the underlying asset to borrow + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function borrowInternal(uint borrowAmount) internal nonReentrant returns (uint) { + uint error = accrueInterest(); + + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed + return fail(Error(error), FailureInfo.BORROW_ACCRUE_INTEREST_FAILED); + } + // borrowFresh emits borrow-specific logs on errors, so we don't need to + return borrowFresh(msg.sender, borrowAmount); + } + + struct BorrowLocalVars { + MathError mathErr; + uint accountBorrows; + uint accountBorrowsNew; + uint totalBorrowsNew; + } + + /** + * @notice Users borrow assets from the protocol to their own address + * @param borrowAmount The amount of the underlying asset to borrow + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function borrowFresh(address payable borrower, uint borrowAmount) internal returns (uint) { + /* Fail if borrow not allowed */ + uint allowed = comptroller.borrowAllowed(address(this), borrower, borrowAmount); + + if (allowed != 0) { + return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.BORROW_COMPTROLLER_REJECTION, allowed); + } + + + /* Verify market's block number equals current block number */ + if (accrualBlockNumber != getBlockNumber()) { + return fail(Error.MARKET_NOT_FRESH, FailureInfo.BORROW_FRESHNESS_CHECK); + } + + /* Fail gracefully if protocol has insufficient underlying cash */ + if (getCashPrior() < borrowAmount) { + return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.BORROW_CASH_NOT_AVAILABLE); + } + + BorrowLocalVars memory vars; + + /* + * We calculate the new borrower and total borrow balances, failing on overflow: + * accountBorrowsNew = accountBorrows + borrowAmount + * totalBorrowsNew = totalBorrows + borrowAmount + */ + (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); + } + + (vars.mathErr, vars.accountBorrowsNew) = addUInt(vars.accountBorrows, borrowAmount); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); + } + + (vars.mathErr, vars.totalBorrowsNew) = addUInt(totalBorrows, borrowAmount); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* + * We invoke doTransferOut for the borrower and the borrowAmount. + * Note: The cToken must handle variations between ERC-20 and ETH underlying. + * On success, the cToken borrowAmount less of cash. + * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred. + */ + // ruleid: compound-borrowfresh-reentrancy + doTransferOut(borrower, borrowAmount); + + /* We write the previously calculated values into storage */ + accountBorrows[borrower].principal = vars.accountBorrowsNew; + accountBorrows[borrower].interestIndex = borrowIndex; + totalBorrows = vars.totalBorrowsNew; + + /* We emit a Borrow event */ + emit Borrow(borrower, borrowAmount, vars.accountBorrowsNew, vars.totalBorrowsNew); + + /* We call the defense hook */ + // unused function + // Comptroller.borrowVerify(address(this), borrower, borrowAmount); + + return uint(Error.NO_ERROR); + } + + /** + * @notice Sender repays their own borrow + * @param repayAmount The amount to repay + * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount. + */ + function repayBorrowInternal(uint repayAmount) internal nonReentrant returns (uint, uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed + return (fail(Error(error), FailureInfo.REPAY_BORROW_ACCRUE_INTEREST_FAILED), 0); + } + // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to + return repayBorrowFresh(msg.sender, msg.sender, repayAmount); + } + + /** + * @notice Sender repays a borrow belonging to borrower + * @param borrower the account with the debt being payed off + * @param repayAmount The amount to repay + * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount. + */ + function repayBorrowBehalfInternal(address borrower, uint repayAmount) internal nonReentrant returns (uint, uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed + return (fail(Error(error), FailureInfo.REPAY_BEHALF_ACCRUE_INTEREST_FAILED), 0); + } + // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to + return repayBorrowFresh(msg.sender, borrower, repayAmount); + } + + struct RepayBorrowLocalVars { + Error err; + MathError mathErr; + uint repayAmount; + uint borrowerIndex; + uint accountBorrows; + uint accountBorrowsNew; + uint totalBorrowsNew; + uint actualRepayAmount; + } + + /** + * @notice Borrows are repaid by another user (possibly the borrower). + * @param payer the account paying off the borrow + * @param borrower the account with the debt being payed off + * @param repayAmount the amount of undelrying tokens being returned + * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount. + */ + function repayBorrowFresh(address payer, address borrower, uint repayAmount) internal returns (uint, uint) { + /* Fail if repayBorrow not allowed */ + uint allowed = comptroller.repayBorrowAllowed(address(this), payer, borrower, repayAmount); + if (allowed != 0) { + return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.REPAY_BORROW_COMPTROLLER_REJECTION, allowed), 0); + } + + /* Verify market's block number equals current block number */ + if (accrualBlockNumber != getBlockNumber()) { + return (fail(Error.MARKET_NOT_FRESH, FailureInfo.REPAY_BORROW_FRESHNESS_CHECK), 0); + } + + RepayBorrowLocalVars memory vars; + + /* We remember the original borrowerIndex for verification purposes */ + vars.borrowerIndex = accountBorrows[borrower].interestIndex; + + /* We fetch the amount the borrower owes, with accumulated interest */ + (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower); + if (vars.mathErr != MathError.NO_ERROR) { + return (failOpaque(Error.MATH_ERROR, FailureInfo.REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)), 0); + } + + /* If repayAmount == -1, repayAmount = accountBorrows */ + if (repayAmount == uint(-1)) { + vars.repayAmount = vars.accountBorrows; + } else { + vars.repayAmount = repayAmount; + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* + * We call doTransferIn for the payer and the repayAmount + * Note: The cToken must handle variations between ERC-20 and ETH underlying. + * On success, the cToken holds an additional repayAmount of cash. + * doTransferIn reverts if anything goes wrong, since we can't be sure if side effects occurred. + * it returns the amount actually transferred, in case of a fee. + */ + vars.actualRepayAmount = doTransferIn(payer, vars.repayAmount); + + /* + * We calculate the new borrower and total borrow balances, failing on underflow: + * accountBorrowsNew = accountBorrows - actualRepayAmount + * totalBorrowsNew = totalBorrows - actualRepayAmount + */ + (vars.mathErr, vars.accountBorrowsNew) = subUInt(vars.accountBorrows, vars.actualRepayAmount); + require(vars.mathErr == MathError.NO_ERROR, "REPAY_BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED"); + + (vars.mathErr, vars.totalBorrowsNew) = subUInt(totalBorrows, vars.actualRepayAmount); + require(vars.mathErr == MathError.NO_ERROR, "REPAY_BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED"); + + /* We write the previously calculated values into storage */ + accountBorrows[borrower].principal = vars.accountBorrowsNew; + accountBorrows[borrower].interestIndex = borrowIndex; + totalBorrows = vars.totalBorrowsNew; + + /* We emit a RepayBorrow event */ + emit RepayBorrow(payer, borrower, vars.actualRepayAmount, vars.accountBorrowsNew, vars.totalBorrowsNew); + + /* We call the defense hook */ + // unused function + // Comptroller.repayBorrowVerify(address(this), payer, borrower, vars.actualRepayAmount, vars.borrowerIndex); + + return (uint(Error.NO_ERROR), vars.actualRepayAmount); + } + + /** + * @notice The sender liquidates the borrowers collateral. + * The collateral seized is transferred to the liquidator. + * @param borrower The borrower of this cToken to be liquidated + * @param cTokenCollateral The market in which to seize collateral from the borrower + * @param repayAmount The amount of the underlying borrowed asset to repay + * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount. + */ + function liquidateBorrowInternal(address borrower, uint repayAmount, CTokenInterface cTokenCollateral) internal nonReentrant returns (uint, uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed + return (fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED), 0); + } + + error = cTokenCollateral.accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed + return (fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED), 0); + } + + // liquidateBorrowFresh emits borrow-specific logs on errors, so we don't need to + return liquidateBorrowFresh(msg.sender, borrower, repayAmount, cTokenCollateral); + } + + /** + * @notice The liquidator liquidates the borrowers collateral. + * The collateral seized is transferred to the liquidator. + * @param borrower The borrower of this cToken to be liquidated + * @param liquidator The address repaying the borrow and seizing collateral + * @param cTokenCollateral The market in which to seize collateral from the borrower + * @param repayAmount The amount of the underlying borrowed asset to repay + * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount. + */ + function liquidateBorrowFresh(address liquidator, address borrower, uint repayAmount, CTokenInterface cTokenCollateral) internal returns (uint, uint) { + /* Fail if liquidate not allowed */ + uint allowed = comptroller.liquidateBorrowAllowed(address(this), address(cTokenCollateral), liquidator, borrower, repayAmount); + if (allowed != 0) { + return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_COMPTROLLER_REJECTION, allowed), 0); + } + + /* Verify market's block number equals current block number */ + if (accrualBlockNumber != getBlockNumber()) { + return (fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_FRESHNESS_CHECK), 0); + } + + /* Verify cTokenCollateral market's block number equals current block number */ + if (cTokenCollateral.getAccrualBlockNumber() != getBlockNumber()) { + return (fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_COLLATERAL_FRESHNESS_CHECK), 0); + } + + /* Fail if borrower = liquidator */ + if (borrower == liquidator) { + return (fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_LIQUIDATOR_IS_BORROWER), 0); + } + + /* Fail if repayAmount = 0 */ + if (repayAmount == 0) { + return (fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_ZERO), 0); + } + + /* Fail if repayAmount = -1 */ + if (repayAmount == uint(-1)) { + return (fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX), 0); + } + + + /* Fail if repayBorrow fails */ + (uint repayBorrowError, uint actualRepayAmount) = repayBorrowFresh(liquidator, borrower, repayAmount); + if (repayBorrowError != uint(Error.NO_ERROR)) { + return (fail(Error(repayBorrowError), FailureInfo.LIQUIDATE_REPAY_BORROW_FRESH_FAILED), 0); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* We calculate the number of collateral tokens that will be seized */ + (uint amountSeizeError, uint seizeTokens) = comptroller.liquidateCalculateSeizeTokens(address(this), address(cTokenCollateral), actualRepayAmount); + require(amountSeizeError == uint(Error.NO_ERROR), "LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED"); + + /* Revert if borrower collateral token balance < seizeTokens */ + require(cTokenCollateral.balanceOf(borrower) >= seizeTokens, "LIQUIDATE_SEIZE_TOO_MUCH"); + + // If this is also the collateral, run seizeInternal to avoid re-entrancy, otherwise make an external call + uint seizeError; + if (address(cTokenCollateral) == address(this)) { + seizeError = seizeInternal(address(this), liquidator, borrower, seizeTokens); + } else { + seizeError = cTokenCollateral.seize(liquidator, borrower, seizeTokens); + } + + /* Revert if seize tokens fails (since we cannot be sure of side effects) */ + require(seizeError == uint(Error.NO_ERROR), "token seizure failed"); + + /* We emit a LiquidateBorrow event */ + emit LiquidateBorrow(liquidator, borrower, actualRepayAmount, address(cTokenCollateral), seizeTokens); + + /* We call the defense hook */ + // unused function + // Comptroller.liquidateBorrowVerify(address(this), address(cTokenCollateral), liquidator, borrower, actualRepayAmount, seizeTokens); + + return (uint(Error.NO_ERROR), actualRepayAmount); + } + + /** + * @notice Transfers collateral tokens (this market) to the liquidator. + * @dev Will fail unless called by another cToken during the process of liquidation. + * Its absolutely critical to use msg.sender as the borrowed cToken and not a parameter. + * @param liquidator The account receiving seized collateral + * @param borrower The account having collateral seized + * @param seizeTokens The number of cTokens to seize + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function seize(address liquidator, address borrower, uint seizeTokens) external nonReentrant returns (uint) { + return seizeInternal(msg.sender, liquidator, borrower, seizeTokens); + } + + /** + * @notice Transfers collateral tokens (this market) to the liquidator. + * @dev Called only during an in-kind liquidation, or by liquidateBorrow during the liquidation of another CToken. + * Its absolutely critical to use msg.sender as the seizer cToken and not a parameter. + * @param seizerToken The contract seizing the collateral (i.e. borrowed cToken) + * @param liquidator The account receiving seized collateral + * @param borrower The account having collateral seized + * @param seizeTokens The number of cTokens to seize + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function seizeInternal(address seizerToken, address liquidator, address borrower, uint seizeTokens) internal returns (uint) { + /* Fail if seize not allowed */ + uint allowed = comptroller.seizeAllowed(address(this), seizerToken, liquidator, borrower, seizeTokens); + if (allowed != 0) { + return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_SEIZE_COMPTROLLER_REJECTION, allowed); + } + + /* Fail if borrower = liquidator */ + if (borrower == liquidator) { + return fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER); + } + + MathError mathErr; + uint borrowerTokensNew; + uint liquidatorTokensNew; + + /* + * We calculate the new borrower and liquidator token balances, failing on underflow/overflow: + * borrowerTokensNew = accountTokens[borrower] - seizeTokens + * liquidatorTokensNew = accountTokens[liquidator] + seizeTokens + */ + (mathErr, borrowerTokensNew) = subUInt(accountTokens[borrower], seizeTokens); + if (mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED, uint(mathErr)); + } + + (mathErr, liquidatorTokensNew) = addUInt(accountTokens[liquidator], seizeTokens); + if (mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED, uint(mathErr)); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* We write the previously calculated values into storage */ + accountTokens[borrower] = borrowerTokensNew; + accountTokens[liquidator] = liquidatorTokensNew; + + /* Emit a Transfer event */ + emit Transfer(borrower, liquidator, seizeTokens); + + /* We call the defense hook */ + // Transfer verify is required here due to tokens being transferred, and have to keep the + // ACC accounting in check + // This works, because the 'borrower' has to be in this market. and so, the active collateral usage can either remain unchanged + // (if the liquidator is also in the market) or reduce (if the liquidator is not in the market) + comptroller.transferVerify(address(this), borrower, liquidator, seizeTokens); + + /* We call the defense hook */ + // unused function + // Comptroller.seizeVerify(address(this), seizerToken, liquidator, borrower, seizeTokens); + + return uint(Error.NO_ERROR); + } + + + /*** Admin Functions ***/ + + /** + * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. + * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. + * @param newPendingAdmin New pending admin. + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setPendingAdmin(address payable newPendingAdmin) external returns (uint) { + // Check caller = admin + if (msg.sender != admin) { + return fail(Error.UNAUTHORIZED, FailureInfo.SET_PENDING_ADMIN_OWNER_CHECK); + } + + // Save current value, if any, for inclusion in log + address oldPendingAdmin = pendingAdmin; + + // Store pendingAdmin with value newPendingAdmin + pendingAdmin = newPendingAdmin; + + // Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin) + emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin); + + return uint(Error.NO_ERROR); + } + + /** + * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin + * @dev Admin function for pending admin to accept role and update admin + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _acceptAdmin() external returns (uint) { + // Check caller is pendingAdmin and pendingAdmin ≠ address(0) + if (msg.sender != pendingAdmin || msg.sender == address(0)) { + return fail(Error.UNAUTHORIZED, FailureInfo.ACCEPT_ADMIN_PENDING_ADMIN_CHECK); + } + + // Save current values for inclusion in log + address oldAdmin = admin; + address oldPendingAdmin = pendingAdmin; + + // Store admin with value pendingAdmin + admin = pendingAdmin; + + // Clear the pending value + pendingAdmin = address(0); + + emit NewAdmin(oldAdmin, admin); + emit NewPendingAdmin(oldPendingAdmin, pendingAdmin); + + return uint(Error.NO_ERROR); + } + + /** + * OLA_ADDITIONS : Made internal and removes Admin check. + * @notice Sets a new Comptroller for the market + * @dev Admin function to set a new Comptroller + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setComptroller(ComptrollerInterface newComptroller) internal returns (uint) { + ComptrollerInterface oldComptroller = comptroller; + // Ensure invoke Comptroller.isComptroller() returns true + require(newComptroller.isComptroller(), "marker method returned false"); + + // Set market's Comptroller to newComptroller + comptroller = newComptroller; + + // Emit NewComptroller(oldComptroller, newComptroller) + emit NewComptroller(oldComptroller, newComptroller); + + return uint(Error.NO_ERROR); + } + + /** + * @notice accrues interest and sets a new reserve factor for the protocol using _setReserveFactorFresh + * @dev Admin function to accrue interest and set a new reserve factor + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setReserveFactor(uint newReserveFactorMantissa) external nonReentrant returns (uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reserve factor change failed. + return fail(Error(error), FailureInfo.SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED); + } + // _setReserveFactorFresh emits reserve-factor-specific logs on errors, so we don't need to. + return _setReserveFactorFresh(newReserveFactorMantissa); + } + + /** + * @notice Sets a new reserve factor for the protocol (*requires fresh interest accrual) + * @dev Admin function to set a new reserve factor + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setReserveFactorFresh(uint newReserveFactorMantissa) internal returns (uint) { + // Check caller is admin + if (msg.sender != admin) { + return fail(Error.UNAUTHORIZED, FailureInfo.SET_RESERVE_FACTOR_ADMIN_CHECK); + } + + // Verify market's block number equals current block number + if (accrualBlockNumber != getBlockNumber()) { + return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_RESERVE_FACTOR_FRESH_CHECK); + } + + // Check newReserveFactor ≤ maxReserveFactor + if (newReserveFactorMantissa > reserveFactorMaxMantissa) { + return fail(Error.BAD_INPUT, FailureInfo.SET_RESERVE_FACTOR_BOUNDS_CHECK); + } + + // OLA_ADDITIONS :This constraint + // Check newReserveFactor >= minReserveFactor + if (newReserveFactorMantissa < reserveFactorMinMantissa) { + return fail(Error.BAD_INPUT, FailureInfo.SET_RESERVE_FACTOR_BOUNDS_CHECK); + } + + uint oldReserveFactorMantissa = reserveFactorMantissa; + reserveFactorMantissa = newReserveFactorMantissa; + + emit NewReserveFactor(oldReserveFactorMantissa, newReserveFactorMantissa); + + return uint(Error.NO_ERROR); + } + + /** + * @notice Accrues interest and reduces reserves by transferring to admin + * @param reduceAmount Amount of reduction to reserves + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _reduceReserves(uint reduceAmount) external nonReentrant returns (uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reduce reserves failed. + return fail(Error(error), FailureInfo.REDUCE_RESERVES_ACCRUE_INTEREST_FAILED); + } + + + + // _reduceReservesFresh emits reserve-reduction-specific logs on errors, so we don't need to. + return _reduceReservesFresh(reduceAmount); + } + + /** + * @notice Reduces reserves by transferring to the LeN admin and to Ola bank their respective shares + * @dev Requires fresh interest accrual + * @param reduceAmount Amount of reduction to reserves + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _reduceReservesFresh(uint reduceAmount) internal returns (uint) { + // totalReserves - reduceAmount + uint totalReservesNew; + + // OLA_ADDITIONS : Allowing anyone to reduce reserves + // Check caller is admin + // if (msg.sender != admin) { + // return fail(Error.UNAUTHORIZED, FailureInfo.REDUCE_RESERVES_ADMIN_CHECK); + // } + + // We fail gracefully unless market's block number equals current block number + if (accrualBlockNumber != getBlockNumber()) { + return fail(Error.MARKET_NOT_FRESH, FailureInfo.REDUCE_RESERVES_FRESH_CHECK); + } + + // Fail gracefully if protocol has insufficient underlying cash + if (getCashPrior() < reduceAmount) { + return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.REDUCE_RESERVES_CASH_NOT_AVAILABLE); + } + + // Check reduceAmount ≤ reserves[n] (totalReserves) + if (reduceAmount > totalReserves) { + return fail(Error.BAD_INPUT, FailureInfo.REDUCE_RESERVES_VALIDATION); + } + + // OLA_ADDITIONS : Dividing the reduced amount between the Admin and Ola (+validations) + // Important to notice that we have added Math calculations to this function. + // Where as before, it only used pre-calculated numbers. + MathError mathErr; + uint adminPart; + uint olaPart; + uint olaReserveFactorMantissa = fetchOlaReserveFactorMantissa(); + address payable olaBankAddress = fetchOlaBankAddress(); + address payable adminBankAddress = fetchAdminBankAddress(); + + // Calculate olaPart + (mathErr, olaPart) = mulScalarTruncate(Exp({mantissa: olaReserveFactorMantissa}), reduceAmount); + if (mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDUCE_RESERVES_OLA_PART_CALCULATION_FAILED, uint(mathErr)); + } + + // Sanity check, should never be a problem in a well parameterized system + if (olaPart >= reduceAmount) { + return fail(Error.BAD_SYSTEM_PARAMS, FailureInfo.REDUCE_RESERVES_OLA_PART_CALCULATION_FAILED); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + // Calculate admin part + adminPart = reduceAmount - olaPart; + // We checked olaPart < reduceAmount above, so this should never revert. + require(adminPart < reduceAmount, "reduce reserves unexpected adminPart underflow"); + + totalReservesNew = totalReserves - reduceAmount; + // We checked reduceAmount <= totalReserves above, so this should never revert. + require(totalReservesNew <= totalReserves, "reduce reserves unexpected underflow"); + + // Store reserves[n+1] = reserves[n] - reduceAmount + totalReserves = totalReservesNew; + + // OLA_ADDITIONS : Transfer reserves to both admin and Ola bank addresses + // doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred. + doTransferOut(adminBankAddress, adminPart); + doTransferOut(olaBankAddress, olaPart); + + emit ReservesReduced(adminBankAddress, adminPart, olaBankAddress, olaPart, totalReservesNew); + + return uint(Error.NO_ERROR); + } + + /** + * @notice accrues interest and updates the interest rate model using _setInterestRateModelFresh + * @dev Admin function to accrue interest and update the interest rate model + * @param newInterestRateModel the new interest rate model to use + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setInterestRateModel(InterestRateModel newInterestRateModel) public returns (uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted change of interest rate model failed + return fail(Error(error), FailureInfo.SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED); + } + // _setInterestRateModelFresh emits interest-rate-model-update-specific logs on errors, so we don't need to. + return _setInterestRateModelFresh(newInterestRateModel); + } + + /** + * @notice updates the interest rate model (*requires fresh interest accrual) + * @dev Admin function to update the interest rate model + * @param newInterestRateModel the new interest rate model to use + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setInterestRateModelFresh(InterestRateModel newInterestRateModel) internal returns (uint) { + + // Used to store old model for use in the event that is emitted on success + InterestRateModel oldInterestRateModel; + + // Check caller is admin + if (msg.sender != admin) { + return fail(Error.UNAUTHORIZED, FailureInfo.SET_INTEREST_RATE_MODEL_OWNER_CHECK); + } + + // We fail gracefully unless market's block number equals current block number + if (accrualBlockNumber != getBlockNumber()) { + return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_INTEREST_RATE_MODEL_FRESH_CHECK); + } + + // Ensure interest rate model is an approved contracts + RegistryForOToken registry = RegistryForOToken(comptroller.getRegistry()); + + require(registry.isSupportedInterestRateModel(address(newInterestRateModel)), "Unapproved interest rate model"); + + // Track the market's current interest rate model + oldInterestRateModel = interestRateModel; + + // Ensure invoke newInterestRateModel.isInterestRateModel() returns true + require(newInterestRateModel.isInterestRateModel(), "marker method returned false"); + + // Set the interest rate model to newInterestRateModel + interestRateModel = newInterestRateModel; + + // Emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel) + emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel); + + return uint(Error.NO_ERROR); + } + + /*** Safe Token ***/ + + /** + * @notice Gets balance of this contract in terms of the underlying + * @dev This excludes the value of the current message, if any + * @return The quantity of underlying owned by this contract + */ + function getCashPrior() internal view returns (uint); + + /** + * @dev Performs a transfer in, reverting upon failure. Returns the amount actually transferred to the protocol, in case of a fee. + * This may revert due to insufficient balance or insufficient allowance. + */ + function doTransferIn(address from, uint amount) internal returns (uint); + + /** + * @dev Performs a transfer out, ideally returning an explanatory error code upon failure tather than reverting. + * If caller has not called checked protocol's balance, may revert due to insufficient cash held in the contract. + * If caller has checked protocol's balance, and verified it is >= amount, this should not revert in normal conditions. + */ + function doTransferOut(address payable to, uint amount) internal; + + /** + * OLA_ADDITIONS: This function + * @dev Returns the ola reserves factor. + */ + function fetchOlaReserveFactorMantissa() internal pure returns (uint) { + return olaReserveFactorMantissa; + } + + /** + * OLA_ADDITIONS: This function + * @dev Fetches the ola bank address. + */ + function fetchOlaBankAddress() internal returns (address payable) { + return RegistryForOToken(comptroller.getRegistry()).olaBankAddress(); + } + + /** + * OLA_ADDITIONS: This function + * @dev Fetches the admin bank address. + */ + function fetchAdminBankAddress() internal view returns (address payable) { + return ComptrollerForOToken(address(comptroller)).adminBankAddress(); + } + + /*** Reentrancy Guard ***/ + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + */ + modifier nonReentrant() { + require(_notEntered, "re-entered"); + _notEntered = false; + _; + _notEntered = true; // get a gas-refund post-Istanbul + } +} + + +contract ComptrollerInterface { + /// @notice Indicator that this is a Comptroller contract (for inspection) + bool public constant isComptroller = true; + + /*** OLA_ADDITIONS : registry getter ***/ + /*** Registry ***/ + function getRegistry() external view returns (address); + + /*** Assets supported by the Comptroller ***/ + function getAllMarkets() public view returns (CToken[] memory); + + /*** OLA_ADDITIONS : peripheral checkers ***/ + /*** Peripherals ***/ + function hasRainMaker() view public returns (bool); + function hasBouncer() view public returns (bool); + + /*** Assets You Are In ***/ + + function enterMarkets(address[] calldata cTokens) external returns (uint[] memory); + function exitMarket(address cToken) external returns (uint); + + /*** Policy Hooks ***/ + + function mintAllowed(address cToken, address minter, uint mintAmount) external returns (uint); + function mintVerify(address cToken, address minter, uint mintAmount, uint mintTokens) external; + + function redeemAllowed(address cToken, address redeemer, uint redeemTokens) external returns (uint); + function redeemVerify(address cToken, address redeemer, uint redeemAmount, uint redeemTokens) external; + + function borrowAllowed(address cToken, address borrower, uint borrowAmount) external returns (uint); + function borrowVerify(address cToken, address borrower, uint borrowAmount) external; + + function repayBorrowAllowed( + address cToken, + address payer, + address borrower, + uint repayAmount) external returns (uint); + function repayBorrowVerify( + address cToken, + address payer, + address borrower, + uint repayAmount, + uint borrowerIndex) external; + + function liquidateBorrowAllowed( + address cTokenBorrowed, + address cTokenCollateral, + address liquidator, + address borrower, + uint repayAmount) external returns (uint); + function liquidateBorrowVerify( + address cTokenBorrowed, + address cTokenCollateral, + address liquidator, + address borrower, + uint repayAmount, + uint seizeTokens) external; + + function seizeAllowed( + address cTokenCollateral, + address cTokenBorrowed, + address liquidator, + address borrower, + uint seizeTokens) external returns (uint); + function seizeVerify( + address cTokenCollateral, + address cTokenBorrowed, + address liquidator, + address borrower, + uint seizeTokens) external; + + function transferAllowed(address cToken, address src, address dst, uint transferTokens) external returns (uint); + function transferVerify(address cToken, address src, address dst, uint transferTokens) external; + + /*** Liquidity/Liquidation Calculations ***/ + + function liquidateCalculateSeizeTokens( + address cTokenBorrowed, + address cTokenCollateral, + uint repayAmount) external view returns (uint, uint); +} + + + + +/** + * @title EIP20NonStandardInterface + * @dev Version of ERC20 with no return values for `transfer` and `transferFrom` + * See https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca + */ +interface EIP20NonStandardInterface { + + /** + * @notice Get the total number of tokens in circulation + * @return The supply of tokens + */ + function totalSupply() external view returns (uint256); + + /** + * @notice Gets the balance of the specified address + * @param owner The address from which the balance will be retrieved + * @return The balance + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /// + /// !!!!!!!!!!!!!! + /// !!! NOTICE !!! `transfer` does not return a value, in violation of the ERC-20 specification + /// !!!!!!!!!!!!!! + /// + + /** + * @notice Transfer `amount` tokens from `msg.sender` to `dst` + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + */ + function transfer(address dst, uint256 amount) external; + + /// + /// !!!!!!!!!!!!!! + /// !!! NOTICE !!! `transferFrom` does not return a value, in violation of the ERC-20 specification + /// !!!!!!!!!!!!!! + /// + + /** + * @notice Transfer `amount` tokens from `src` to `dst` + * @param src The address of the source account + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + */ + function transferFrom(address src, address dst, uint256 amount) external; + + /** + * @notice Approve `spender` to transfer up to `amount` from `src` + * @dev This will overwrite the approval amount for `spender` + * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) + * @param spender The address of the account which may transfer tokens + * @param amount The number of tokens that are approved + * @return Whether or not the approval succeeded + */ + function approve(address spender, uint256 amount) external returns (bool success); + + /** + * @notice Get the current allowance from `owner` for `spender` + * @param owner The address of the account which owns the tokens to be spent + * @param spender The address of the account which may transfer tokens + * @return The number of tokens allowed to be spent + */ + function allowance(address owner, address spender) external view returns (uint256 remaining); + + event Transfer(address indexed from, address indexed to, uint256 amount); + event Approval(address indexed owner, address indexed spender, uint256 amount); +} + + +contract CTokenDelegatorInterface { + + /*** Implementation Events ***/ + + /** + * @notice Emitted when implementation is changed + */ + event NewImplementation(address indexed oldImplementation, address indexed newImplementation); + + /** + * @notice Emitted when implementation is not changed under a system version update + */ + event ImplementationDidNotChange(address indexed implementation); + + + /*** Implementation functions ***/ + + // OLA_ADDITIONS : Update implementation from the Registry + function updateImplementationFromRegistry(bool allowResign, bytes calldata becomeImplementationData) external returns (bool); +} + + + + + +contract CErc20Interface { + /*** User Interface ***/ + + function mint(uint mintAmount) external returns (uint); + function redeem(uint redeemTokens) external returns (uint); + function redeemUnderlying(uint redeemAmount) external returns (uint); + function borrow(uint borrowAmount) external returns (uint); + function repayBorrow(uint repayAmount) external returns (uint); + function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint); + function liquidateBorrow(address borrower, uint repayAmount, CTokenInterface cTokenCollateral) external returns (uint); + function sweepToken(EIP20NonStandardInterface token) external; +} + +contract CErc20StorageV0_01 {} + +contract CErc20StorageV0_02 is CErc20StorageV0_01 {} + +contract ONativeInterface { + /*** User Interface ***/ + + function mint() external payable; + function redeem(uint redeemTokens) external returns (uint); + function redeemUnderlying(uint redeemAmount) external returns (uint); + function borrow(uint borrowAmount) external returns (uint); + function repayBorrow() external payable; + function repayBorrowBehalf(address borrower) external payable; + function liquidateBorrow(address borrower, CTokenInterface cTokenCollateral) external payable; + function sweepToken(EIP20NonStandardInterface token) external; +} + +contract CEtherStorageV0_01 {} + +contract CEtherStorageV0_02 is CEtherStorageV0_01 {} + +contract CDelegateInterface { + /** + * @notice Called by the delegator on a delegate to initialize it for duty + * @dev Should revert if any issues arise which make it unfit for delegation + * @param data The encoded bytes data for any initialization + */ + function _becomeImplementation(bytes memory data) public; + + /** + * @notice Called by the delegator on a delegate to forfeit its responsibility + */ + function _resignImplementation() public; +} + + + + + + +interface RegistryForODelegator { + function getImplementationForLn(address lnUnitroller, bytes32 contractNameHash) external returns (address); +} + +/** + * @title Ola's ODelegator Contract + * @notice OTokens which delegate to an implementation + * @author Ola + */ +contract ODelegator is CTokenAdminStorage, CTokenDelegatorInterface { + + /** + * @notice Called by the Comptroller (most of the time) or by the admin (only via the constructor) to update the + * implementation of the delegator + * @param implementation_ The address of the new implementation for delegation + * @param allowResign Flag to indicate whether to call _resignImplementation on the old implementation + * @param becomeImplementationData The encoded bytes data to be passed to _becomeImplementation + */ + function _setImplementation(address implementation_, bool allowResign, bytes memory becomeImplementationData) internal { + if (allowResign) { + delegateToImplementation(abi.encodeWithSignature("_resignImplementation()")); + } + + // Basic sanity + require(CToken(implementation_).isCToken(), "Not CTokens"); + + address oldImplementation = implementation; + implementation = implementation_; + + + delegateToImplementation(abi.encodeWithSignature("_becomeImplementation(bytes)", becomeImplementationData)); + + emit NewImplementation(oldImplementation, implementation); + } + + /** + * @notice Internal method to delegate execution to another contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + * @param callee The contract to delegatecall + * @param data The raw data to delegatecall + * @return The returned bytes from the delegatecall + */ + function delegateTo(address callee, bytes memory data) internal returns (bytes memory) { + (bool success, bytes memory returnData) = callee.delegatecall(data); + assembly { + if eq(success, 0) { + revert(add(returnData, 0x20), returndatasize) + } + } + return returnData; + } + + /** + * @notice Delegates execution to the implementation contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + * @param data The raw data to delegatecall + * @return The returned bytes from the delegatecall + */ + function delegateToImplementation(bytes memory data) public returns (bytes memory) { + return delegateTo(implementation, data); + } + + /** + * @notice Delegates execution to an implementation contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + * There are an additional 2 prefix uints from the wrapper returndata, which we ignore since we make an extra hop. + * @param data The raw data to delegatecall + * @return The returned bytes from the delegatecall + */ + function delegateToViewImplementation(bytes memory data) public view returns (bytes memory) { + (bool success, bytes memory returnData) = address(this).staticcall(abi.encodeWithSignature("delegateToImplementation(bytes)", data)); + assembly { + if eq(success, 0) { + revert(add(returnData, 0x20), returndatasize) + } + } + return abi.decode(returnData, (bytes)); + } + + function updateImplementationFromRegistry(bool allowResign, bytes calldata becomeImplementationData) external returns (bool) { + require(msg.sender == address(comptroller), "Not comptroller"); + address implementationToSet = RegistryForODelegator(comptroller.getRegistry()).getImplementationForLn(address(comptroller), contractNameHash); + require(implementationToSet != address(0), "No implementation"); + + if (implementationToSet != implementation) { + // New implementations always get set via the setter (post-initialize) + _setImplementation(implementationToSet, allowResign, becomeImplementationData); + } else { + emit ImplementationDidNotChange(implementation); + } + + return true; + } + + /** + * @notice Delegates execution to an implementation contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + */ + function () external payable { + require(msg.value == 0,"ODelegator:fallback: cannot send value to fallback"); + + // delegate all other functions to current implementation + (bool success, ) = implementation.delegatecall(msg.data); + + assembly { + let free_mem_ptr := mload(0x40) + returndatacopy(free_mem_ptr, 0, returndatasize) + + switch success + case 0 { revert(free_mem_ptr, returndatasize) } + default { return(free_mem_ptr, returndatasize) } + } + } +} + +/** + * @title Compound's CErc20Delegator Contract + * @notice CTokens which wrap an EIP-20 underlying and delegate to an implementation + * @author Compound + */ +contract CErc20Delegator is ODelegator, CTokenInterface, CErc20Interface { + // OLA_ADDITIONS : This contract name hash + bytes32 constant public CErc20DelegatorContractHash = keccak256("CErc20Delegator"); + + /** + * @notice Construct a new money market + * @param underlying_ The address of the underlying asset + * @param comptroller_ The address of the Comptroller + * @param interestRateModel_ The address of the interest rate model + * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18 + * @param name_ ERC-20 name of this token + * @param symbol_ ERC-20 symbol of this token + * @param decimals_ ERC-20 decimal precision of this token + * @param admin_ Address of the administrator of this token + * @param becomeImplementationData The encoded args for becomeImplementation + */ + constructor(address underlying_, + ComptrollerInterface comptroller_, + InterestRateModel interestRateModel_, + uint initialExchangeRateMantissa_, + string memory name_, + string memory symbol_, + uint8 decimals_, + address payable admin_, + bytes memory becomeImplementationData) public { + // Creator of the contract is admin during initialization + admin = msg.sender; + + // Initialize name hash + contractNameHash = CErc20DelegatorContractHash; + + address cErc20Implementation = RegistryForODelegator(comptroller_.getRegistry()).getImplementationForLn(address(comptroller_), CErc20DelegatorContractHash); + + // First delegate gets to initialize the delegator (i.e. storage contract) + delegateTo(cErc20Implementation, abi.encodeWithSignature("initialize(address,address,address,uint256,string,string,uint8)", + underlying_, + comptroller_, + interestRateModel_, + initialExchangeRateMantissa_, + name_, + symbol_, + decimals_)); + + // New implementations always get set via the setter (post-initialize) + _setImplementation(cErc20Implementation, false, becomeImplementationData); + + // Set the proper admin now that initialization is done + admin = admin_; + } + + /** + * @notice Sender supplies assets into the market and receives cTokens in exchange + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param mintAmount The amount of the underlying asset to supply + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function mint(uint mintAmount) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("mint(uint256)", mintAmount)); + return abi.decode(data, (uint)); + } + + /** + * @notice Sender redeems cTokens in exchange for the underlying asset + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param redeemTokens The number of cTokens to redeem into underlying + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function redeem(uint redeemTokens) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("redeem(uint256)", redeemTokens)); + return abi.decode(data, (uint)); + } + + /** + * @notice Sender redeems cTokens in exchange for a specified amount of underlying asset + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param redeemAmount The amount of underlying to redeem + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function redeemUnderlying(uint redeemAmount) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("redeemUnderlying(uint256)", redeemAmount)); + return abi.decode(data, (uint)); + } + + /** + * @notice Sender borrows assets from the protocol to their own address + * @param borrowAmount The amount of the underlying asset to borrow + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function borrow(uint borrowAmount) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("borrow(uint256)", borrowAmount)); + return abi.decode(data, (uint)); + } + + /** + * @notice Sender repays their own borrow + * @param repayAmount The amount to repay + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function repayBorrow(uint repayAmount) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("repayBorrow(uint256)", repayAmount)); + return abi.decode(data, (uint)); + } + + /** + * @notice Sender repays a borrow belonging to borrower + * @param borrower the account with the debt being payed off + * @param repayAmount The amount to repay + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("repayBorrowBehalf(address,uint256)", borrower, repayAmount)); + return abi.decode(data, (uint)); + } + + /** + * @notice The sender liquidates the borrowers collateral. + * The collateral seized is transferred to the liquidator. + * @param borrower The borrower of this cToken to be liquidated + * @param cTokenCollateral The market in which to seize collateral from the borrower + * @param repayAmount The amount of the underlying borrowed asset to repay + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function liquidateBorrow(address borrower, uint repayAmount, CTokenInterface cTokenCollateral) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("liquidateBorrow(address,uint256,address)", borrower, repayAmount, cTokenCollateral)); + return abi.decode(data, (uint)); + } + + /** + * @notice Transfer `amount` tokens from `msg.sender` to `dst` + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transfer(address dst, uint amount) external returns (bool) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("transfer(address,uint256)", dst, amount)); + return abi.decode(data, (bool)); + } + + /** + * @notice Transfer `amount` tokens from `src` to `dst` + * @param src The address of the source account + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transferFrom(address src, address dst, uint256 amount) external returns (bool) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("transferFrom(address,address,uint256)", src, dst, amount)); + return abi.decode(data, (bool)); + } + + /** + * @notice Approve `spender` to transfer up to `amount` from `src` + * @dev This will overwrite the approval amount for `spender` + * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) + * @param spender The address of the account which may transfer tokens + * @param amount The number of tokens that are approved (-1 means infinite) + * @return Whether or not the approval succeeded + */ + function approve(address spender, uint256 amount) external returns (bool) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("approve(address,uint256)", spender, amount)); + return abi.decode(data, (bool)); + } + + /** + * @notice Get the current allowance from `owner` for `spender` + * @param owner The address of the account which owns the tokens to be spent + * @param spender The address of the account which may transfer tokens + * @return The number of tokens allowed to be spent (-1 means infinite) + */ + function allowance(address owner, address spender) external view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("allowance(address,address)", owner, spender)); + return abi.decode(data, (uint)); + } + + /** + * @notice Get the token balance of the `owner` + * @param owner The address of the account to query + * @return The number of tokens owned by `owner` + */ + function balanceOf(address owner) external view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("balanceOf(address)", owner)); + return abi.decode(data, (uint)); + } + + /** + * @notice Get the underlying balance of the `owner` + * @dev This also accrues interest in a transaction + * @param owner The address of the account to query + * @return The amount of underlying owned by `owner` + */ + function balanceOfUnderlying(address owner) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("balanceOfUnderlying(address)", owner)); + return abi.decode(data, (uint)); + } + + /** + * @notice Get a snapshot of the account's balances, and the cached exchange rate + * @dev This is used by Comptroller to more efficiently perform liquidity checks. + * @param account Address of the account to snapshot + * @return (possible error, token balance, borrow balance, exchange rate mantissa) + */ + function getAccountSnapshot(address account) external view returns (uint, uint, uint, uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("getAccountSnapshot(address)", account)); + return abi.decode(data, (uint, uint, uint, uint)); + } + + /** + * @notice Returns the current per-block borrow interest rate for this cToken + * @return The borrow interest rate per block, scaled by 1e18 + */ + function borrowRatePerBlock() external view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("borrowRatePerBlock()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Returns the current per-block supply interest rate for this cToken + * @return The supply interest rate per block, scaled by 1e18 + */ + function supplyRatePerBlock() external view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("supplyRatePerBlock()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Returns the current total borrows plus accrued interest + * @return The total borrows with interest + */ + function totalBorrowsCurrent() external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("totalBorrowsCurrent()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex + * @param account The address whose balance should be calculated after updating borrowIndex + * @return The calculated balance + */ + function borrowBalanceCurrent(address account) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("borrowBalanceCurrent(address)", account)); + return abi.decode(data, (uint)); + } + + /** + * @notice Return the borrow balance of account based on stored data + * @param account The address whose balance should be calculated + * @return The calculated balance + */ + function borrowBalanceStored(address account) public view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("borrowBalanceStored(address)", account)); + return abi.decode(data, (uint)); + } + + /** + * @notice Accrue interest then return the up-to-date exchange rate + * @return Calculated exchange rate scaled by 1e18 + */ + function exchangeRateCurrent() public returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("exchangeRateCurrent()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Calculates the exchange rate from the underlying to the CToken + * @dev This function does not accrue interest before calculating the exchange rate + * @return Calculated exchange rate scaled by 1e18 + */ + function exchangeRateStored() public view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("exchangeRateStored()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Get cash balance of this cToken in the underlying asset + * @return The quantity of underlying asset owned by this contract + */ + function getCash() external view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("getCash()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Get the accrual block number of this cToken + * @return The accrual block number + */ + function getAccrualBlockNumber() external view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("getAccrualBlockNumber()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Applies accrued interest to total borrows and reserves. + * @dev This calculates interest accrued from the last checkpointed block + * up to the current block and writes new checkpoint to storage. + */ + function accrueInterest() public returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("accrueInterest()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Transfers collateral tokens (this market) to the liquidator. + * @dev Will fail unless called by another cToken during the process of liquidation. + * Its absolutely critical to use msg.sender as the borrowed cToken and not a parameter. + * @param liquidator The account receiving seized collateral + * @param borrower The account having collateral seized + * @param seizeTokens The number of cTokens to seize + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function seize(address liquidator, address borrower, uint seizeTokens) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("seize(address,address,uint256)", liquidator, borrower, seizeTokens)); + return abi.decode(data, (uint)); + } + + /** + * @notice A public function to sweep accidental ERC-20 transfers to this contract. Tokens are sent to admin (Timelock) + * @param token The address of the ERC-20 token to sweep + */ + function sweepToken(EIP20NonStandardInterface token) external { + delegateToImplementation(abi.encodeWithSignature("sweepToken(address)", token)); + } + + + /*** Admin Functions ***/ + + /** + * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. + * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. + * @param newPendingAdmin New pending admin. + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setPendingAdmin(address payable newPendingAdmin) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("_setPendingAdmin(address)", newPendingAdmin)); + return abi.decode(data, (uint)); + } + + /** + * @notice accrues interest and sets a new reserve factor for the protocol using _setReserveFactorFresh + * @dev Admin function to accrue interest and set a new reserve factor + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setReserveFactor(uint newReserveFactorMantissa) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("_setReserveFactor(uint256)", newReserveFactorMantissa)); + return abi.decode(data, (uint)); + } + + /** + * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin + * @dev Admin function for pending admin to accept role and update admin + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _acceptAdmin() external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("_acceptAdmin()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Accrues interest and reduces reserves by transferring to admin + * @param reduceAmount Amount of reduction to reserves + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _reduceReserves(uint reduceAmount) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("_reduceReserves(uint256)", reduceAmount)); + return abi.decode(data, (uint)); + } + + /** + * @notice Accrues interest and updates the interest rate model using _setInterestRateModelFresh + * @dev Admin function to accrue interest and update the interest rate model + * @param newInterestRateModel the new interest rate model to use + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setInterestRateModel(InterestRateModel newInterestRateModel) public returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("_setInterestRateModel(address)", newInterestRateModel)); + return abi.decode(data, (uint)); + } + + /** + * @notice Internal method to delegate execution to another contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + * @param callee The contract to delegatecall + * @param data The raw data to delegatecall + * @return The returned bytes from the delegatecall + */ + function delegateTo(address callee, bytes memory data) internal returns (bytes memory) { + (bool success, bytes memory returnData) = callee.delegatecall(data); + assembly { + if eq(success, 0) { + revert(add(returnData, 0x20), returndatasize) + } + } + return returnData; + } + + /** + * @notice Delegates execution to the implementation contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + * @param data The raw data to delegatecall + * @return The returned bytes from the delegatecall + */ + function delegateToImplementation(bytes memory data) public returns (bytes memory) { + return delegateTo(implementation, data); + } + + /** + * @notice Delegates execution to an implementation contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + * There are an additional 2 prefix uints from the wrapper returndata, which we ignore since we make an extra hop. + * @param data The raw data to delegatecall + * @return The returned bytes from the delegatecall + */ + function delegateToViewImplementation(bytes memory data) public view returns (bytes memory) { + (bool success, bytes memory returnData) = address(this).staticcall(abi.encodeWithSignature("delegateToImplementation(bytes)", data)); + assembly { + if eq(success, 0) { + revert(add(returnData, 0x20), returndatasize) + } + } + return abi.decode(returnData, (bytes)); + } + + /** + * @notice Delegates execution to an implementation contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + */ + function () external payable { + require(msg.value == 0,"CErc20Delegator:fallback: cannot send value to fallback"); + + // delegate all other functions to current implementation + (bool success, ) = implementation.delegatecall(msg.data); + + assembly { + let free_mem_ptr := mload(0x40) + returndatacopy(free_mem_ptr, 0, returndatasize) + + switch success + case 0 { revert(free_mem_ptr, returndatasize) } + default { return(free_mem_ptr, returndatasize) } + } + } +} + + + diff --git a/semgrep-rules/security/compound-borrowfresh-reentrancy.yaml b/semgrep-rules/security/compound-borrowfresh-reentrancy.yaml new file mode 100644 index 0000000..b5d0754 --- /dev/null +++ b/semgrep-rules/security/compound-borrowfresh-reentrancy.yaml @@ -0,0 +1,32 @@ +rules: + - + id: compound-borrowfresh-reentrancy + message: Function borrowFresh() in Compound performs state update after doTransferOut() + metadata: + category: security + technology: + - solidity + cwe: "CWE-841: Improper Enforcement of Behavioral Workflow" + confidence: HIGH + likelihood: LOW + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/peckshield/status/1509431646818234369 + - https://twitter.com/blocksecteam/status/1509466576848064512 + - https://slowmist.medium.com/another-day-another-reentrancy-attack-5cde10bbb2b4 + - https://explorer.fuse.io/address/0x139Eb08579eec664d461f0B754c1F8B569044611 # Ola + patterns: + - pattern-inside: | + function borrowFresh(...) { + ... + } + - pattern-not-inside: | + accountBorrows[borrower].interestIndex = borrowIndex; + ... + - pattern: doTransferOut(...); + languages: + - solidity + severity: WARNING + diff --git a/semgrep-rules/security/compound-precision-loss.sol b/semgrep-rules/security/compound-precision-loss.sol new file mode 100644 index 0000000..aecc79d --- /dev/null +++ b/semgrep-rules/security/compound-precision-loss.sol @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.10; + + +contract CERC20HundredFinance { + /** + * @notice User redeems cTokens in exchange for the underlying asset + * @dev Assumes interest has already been accrued up to the current block + * @param redeemer The address of the account which is redeeming the tokens + * @param redeemTokensIn The number of cTokens to redeem into underlying (only one of redeemTokensIn or redeemAmountIn may be non-zero) + * @param redeemAmountIn The number of underlying tokens to receive from redeeming cTokens (only one of redeemTokensIn or redeemAmountIn may be non-zero) + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function redeemFresh(address payable redeemer, uint redeemTokensIn, uint redeemAmountIn) internal returns (uint) { + require(redeemTokensIn == 0 || redeemAmountIn == 0, "one of redeemTokensIn or redeemAmountIn must be zero"); + + RedeemLocalVars memory vars; + + /* exchangeRate = invoke Exchange Rate Stored() */ + (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal(); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)); + } + + /* If redeemTokensIn > 0: */ + if (redeemTokensIn > 0) { + /* + * We calculate the exchange rate and the amount of underlying to be redeemed: + * redeemTokens = redeemTokensIn + * redeemAmount = redeemTokensIn x exchangeRateCurrent + */ + vars.redeemTokens = redeemTokensIn; + + (vars.mathErr, vars.redeemAmount) = mulScalarTruncate(Exp({mantissa: vars.exchangeRateMantissa}), redeemTokensIn); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_TOKENS_CALCULATION_FAILED, uint(vars.mathErr)); + } + } else { + /* + * We get the current exchange rate and calculate the amount to be redeemed: + * redeemTokens = redeemAmountIn / exchangeRate + * redeemAmount = redeemAmountIn + */ + + (vars.mathErr, vars.redeemTokens) = divScalarByExpTruncate(redeemAmountIn, Exp({mantissa: vars.exchangeRateMantissa})); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_AMOUNT_CALCULATION_FAILED, uint(vars.mathErr)); + } + //ruleid: compound-precision-loss + vars.redeemAmount = redeemAmountIn; + } + + /* Fail if redeem not allowed */ + uint allowed = comptroller.redeemAllowed(address(this), redeemer, vars.redeemTokens); + if (allowed != 0) { + return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.REDEEM_COMPTROLLER_REJECTION, allowed); + } + + /* Verify market's block number equals current block number */ + if (accrualBlockNumber != getBlockNumber()) { + return fail(Error.MARKET_NOT_FRESH, FailureInfo.REDEEM_FRESHNESS_CHECK); + } + + /* + * We calculate the new total supply and redeemer balance, checking for underflow: + * totalSupplyNew = totalSupply - redeemTokens + * accountTokensNew = accountTokens[redeemer] - redeemTokens + */ + (vars.mathErr, vars.totalSupplyNew) = subUInt(totalSupply, vars.redeemTokens); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_NEW_TOTAL_SUPPLY_CALCULATION_FAILED, uint(vars.mathErr)); + } + + (vars.mathErr, vars.accountTokensNew) = subUInt(accountTokens[redeemer], vars.redeemTokens); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); + } + + /* Fail gracefully if protocol has insufficient cash */ + if (getCashPrior() < vars.redeemAmount) { + return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.REDEEM_TRANSFER_OUT_NOT_POSSIBLE); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* We write previously calculated values into storage */ + totalSupply = vars.totalSupplyNew; + accountTokens[redeemer] = vars.accountTokensNew; + + /* + * We invoke doTransferOut for the redeemer and the redeemAmount. + * Note: The cToken must handle variations between ERC-20 and ETH underlying. + * On success, the cToken has redeemAmount less of cash. + * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred. + */ + doTransferOut(redeemer, vars.redeemAmount); + + /* We emit a Transfer event, and a Redeem event */ + emit Transfer(redeemer, address(this), vars.redeemTokens); + emit Redeem(redeemer, vars.redeemAmount, vars.redeemTokens); + + return uint(Error.NO_ERROR); + } +} + +contract CERC20TwisterFinance { + /** + * @notice User redeems cTokens in exchange for the underlying asset + * @dev Assumes interest has already been accrued up to the current block + * @param redeemer The address of the account which is redeeming the tokens + * @param redeemTokensIn The number of cTokens to redeem into underlying (only one of redeemTokensIn or redeemAmountIn may be non-zero) + * @param redeemAmountIn The number of underlying tokens to receive from redeeming cTokens (only one of redeemTokensIn or redeemAmountIn may be non-zero) + */ + function redeemFresh(address payable redeemer, uint redeemTokensIn, uint redeemAmountIn) internal { + require(redeemTokensIn == 0 || redeemAmountIn == 0, "one of redeemTokensIn or redeemAmountIn must be zero"); + + /* exchangeRate = invoke Exchange Rate Stored() */ + Exp memory exchangeRate = Exp({mantissa: exchangeRateStoredInternal() }); + + uint redeemTokens; + uint redeemAmount; + /* If redeemTokensIn > 0: */ + if (redeemTokensIn > 0) { + /* + * We calculate the exchange rate and the amount of underlying to be redeemed: + * redeemTokens = redeemTokensIn + * redeemAmount = redeemTokensIn x exchangeRateCurrent + */ + redeemTokens = redeemTokensIn; + redeemAmount = mul_ScalarTruncate(exchangeRate, redeemTokensIn); + } else { + /* + * We get the current exchange rate and calculate the amount to be redeemed: + * redeemTokens = redeemAmountIn / exchangeRate + * redeemAmount = redeemAmountIn + */ + redeemTokens = div_(redeemAmountIn, exchangeRate); + // redeemAmount = redeemAmountIn; + // Fix for division attack + //ok: compound-precision-loss + redeemAmount = mul_ScalarTruncate(exchangeRate, redeemTokens); + } + + /* Fail if redeem not allowed */ + uint allowed = comptroller.redeemAllowed(address(this), redeemer, redeemTokens); + if (allowed != 0) { + revert RedeemComptrollerRejection(allowed); + } + + /* Verify market's block number equals current block number */ + if (accrualBlockNumber != getBlockNumber() && !isGLP) { + revert RedeemFreshnessCheck(); + } + + /* Fail gracefully if protocol has insufficient cash */ + if (getCashPrior() < redeemAmount) { + revert RedeemTransferOutNotPossible(); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + + /* + * We write the previously calculated values into storage. + * Note: Avoid token reentrancy attacks by writing reduced supply before external transfer. + */ + totalSupply = totalSupply - redeemTokens; + accountTokens[redeemer] = accountTokens[redeemer] - redeemTokens; + + /* + * We invoke doTransferOut for the redeemer and the redeemAmount. + * Note: The cToken must handle variations between ERC-20 and ETH underlying. + * On success, the cToken has redeemAmount less of cash. + * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred. + */ + bool isRedeemerVip = comptroller.getIsAccountVip(redeemer); + + if(isGLP && !isRedeemerVip && withdrawFee > 0){ + uint256 actualRedeemAmount = div_(mul_(redeemAmount, sub_(10000, withdrawFee)), 10000); + uint256 withdrawFeeAmount = sub_(redeemAmount, actualRedeemAmount); + doTransferOut(redeemer, actualRedeemAmount); + doTransferOut(admin, withdrawFeeAmount); + } else { + doTransferOut(redeemer, redeemAmount); + } + + /* We emit a Transfer event, and a Redeem event */ + emit Transfer(redeemer, address(this), redeemTokens); + emit Redeem(redeemer, redeemAmount, redeemTokens); + + /* We call the defense hook */ + comptroller.redeemVerify(address(this), redeemer, redeemAmount, redeemTokens); + } +} + +contract CERC20OnyxFinance { + /** + * @notice User redeems oTokens in exchange for the underlying asset + * @dev Assumes interest has already been accrued up to the current block + * @param redeemer The address of the account which is redeeming the tokens + * @param redeemTokensIn The number of oTokens to redeem into underlying (only one of redeemTokensIn or redeemAmountIn may be non-zero) + * @param redeemAmountIn The number of underlying tokens to receive from redeeming oTokens (only one of redeemTokensIn or redeemAmountIn may be non-zero) + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function redeemFresh(address payable redeemer, uint redeemTokensIn, uint redeemAmountIn) internal returns (uint) { + require(redeemTokensIn == 0 || redeemAmountIn == 0, "one of redeemTokensIn or redeemAmountIn must be zero"); + + RedeemLocalVars memory vars; + + /* exchangeRate = invoke Exchange Rate Stored() */ + (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal(); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)); + } + + /* If redeemTokensIn > 0: */ + if (redeemTokensIn > 0) { + /* + * We calculate the exchange rate and the amount of underlying to be redeemed: + * redeemTokens = redeemTokensIn + * redeemAmount = redeemTokensIn x exchangeRateCurrent + */ + vars.redeemTokens = redeemTokensIn; + + (vars.mathErr, vars.redeemAmount) = mulScalarTruncate(Exp({mantissa: vars.exchangeRateMantissa}), redeemTokensIn); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_TOKENS_CALCULATION_FAILED, uint(vars.mathErr)); + } + } else { + /* + * We get the current exchange rate and calculate the amount to be redeemed: + * redeemTokens = redeemAmountIn / exchangeRate + * redeemAmount = redeemAmountIn + */ + + (vars.mathErr, vars.redeemTokens) = divScalarByExpTruncate(redeemAmountIn, Exp({mantissa: vars.exchangeRateMantissa})); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_AMOUNT_CALCULATION_FAILED, uint(vars.mathErr)); + } + //ruleid: compound-precision-loss + vars.redeemAmount = redeemAmountIn; + } + + /* Fail if redeem not allowed */ + uint allowed = comptroller.redeemAllowed(address(this), redeemer, vars.redeemTokens); + if (allowed != 0) { + return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.REDEEM_COMPTROLLER_REJECTION, allowed); + } + + /* Verify market's block number equals current block number */ + if (accrualBlockNumber != getBlockNumber()) { + return fail(Error.MARKET_NOT_FRESH, FailureInfo.REDEEM_FRESHNESS_CHECK); + } + + /* + * We calculate the new total supply and redeemer balance, checking for underflow: + * totalSupplyNew = totalSupply - redeemTokens + * accountTokensNew = accountTokens[redeemer] - redeemTokens + */ + (vars.mathErr, vars.totalSupplyNew) = subUInt(totalSupply, vars.redeemTokens); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_NEW_TOTAL_SUPPLY_CALCULATION_FAILED, uint(vars.mathErr)); + } + + (vars.mathErr, vars.accountTokensNew) = subUInt(accountTokens[redeemer], vars.redeemTokens); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); + } + + /* Fail gracefully if protocol has insufficient cash */ + if (getCashPrior() < vars.redeemAmount) { + return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.REDEEM_TRANSFER_OUT_NOT_POSSIBLE); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* We write previously calculated values into storage */ + totalSupply = vars.totalSupplyNew; + accountTokens[redeemer] = vars.accountTokensNew; + + /* + * We invoke doTransferOut for the redeemer and the redeemAmount. + * Note: The oToken must handle variations between ERC-20 and ETH underlying. + * On success, the oToken has redeemAmount less of cash. + * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred. + */ + doTransferOut(redeemer, vars.redeemAmount); + + /* We emit a Transfer event, and a Redeem event */ + emit Transfer(redeemer, address(this), vars.redeemTokens); + emit Redeem(redeemer, vars.redeemAmount, vars.redeemTokens); + + /* We call the defense hook */ + comptroller.redeemVerify(address(this), redeemer, vars.redeemAmount, vars.redeemTokens); + + return uint(Error.NO_ERROR); + } +} diff --git a/semgrep-rules/security/compound-precision-loss.yaml b/semgrep-rules/security/compound-precision-loss.yaml new file mode 100644 index 0000000..215e05a --- /dev/null +++ b/semgrep-rules/security/compound-precision-loss.yaml @@ -0,0 +1,30 @@ +rules: + - + id: compound-precision-loss + message: In Compound forks if there is a market with totalSupply = 0 and collateralFactor != 0 a precision loss attack is possible if redeemAmount is taken from the arguments of redeemFresh() + metadata: + category: security + technology: + - solidity + cwe: "CWE-1339: Insufficient Precision or Accuracy of a Real Number" + confidence: HIGH + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/DecurityHQ/status/1719657969925677161 + - https://defimon.xyz/attack/mainnet/0xf7c21600452939a81b599017ee24ee0dfd92aaaccd0a55d02819a7658a6ef635 + - https://blog.hundred.finance/15-04-23-hundred-finance-hack-post-mortem-d895b618cf33 + + patterns: + - pattern-inside: | + function redeemFresh(...) internal { + ... + } + - pattern-either: + - pattern: redeemAmount = redeemAmountIn; + - pattern: $VARS.redeemAmount = redeemAmountIn; + languages: + - solidity + severity: ERROR diff --git a/semgrep-rules/security/compound-sweeptoken-not-restricted.sol b/semgrep-rules/security/compound-sweeptoken-not-restricted.sol new file mode 100644 index 0000000..47d4410 --- /dev/null +++ b/semgrep-rules/security/compound-sweeptoken-not-restricted.sol @@ -0,0 +1,230 @@ +pragma solidity ^0.5.16; + +import "./CToken.sol"; + +interface CompLike { + function delegate(address delegatee) external; +} + +/** + * @title Compound's CErc20 Contract + * @notice CTokens which wrap an EIP-20 underlying + * @author Compound + */ +contract CErc20 is CToken, CErc20Interface { + /** + * @notice Initialize the new money market + * @param underlying_ The address of the underlying asset + * @param comptroller_ The address of the Comptroller + * @param interestRateModel_ The address of the interest rate model + * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18 + * @param name_ ERC-20 name of this token + * @param symbol_ ERC-20 symbol of this token + * @param decimals_ ERC-20 decimal precision of this token + */ + function initialize(address underlying_, + ComptrollerInterface comptroller_, + InterestRateModel interestRateModel_, + uint initialExchangeRateMantissa_, + string memory name_, + string memory symbol_, + uint8 decimals_) public { + // CToken initialize does the bulk of the work + super.initialize(comptroller_, interestRateModel_, initialExchangeRateMantissa_, name_, symbol_, decimals_); + + // Set underlying and sanity check it + underlying = underlying_; + EIP20Interface(underlying).totalSupply(); + } + + /*** User Interface ***/ + + /** + * @notice Sender supplies assets into the market and receives cTokens in exchange + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param mintAmount The amount of the underlying asset to supply + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function mint(uint mintAmount) external returns (uint) { + (uint err,) = mintInternal(mintAmount); + return err; + } + + /** + * @notice Sender redeems cTokens in exchange for the underlying asset + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param redeemTokens The number of cTokens to redeem into underlying + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function redeem(uint redeemTokens) external returns (uint) { + return redeemInternal(redeemTokens); + } + + /** + * @notice Sender redeems cTokens in exchange for a specified amount of underlying asset + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param redeemAmount The amount of underlying to redeem + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function redeemUnderlying(uint redeemAmount) external returns (uint) { + return redeemUnderlyingInternal(redeemAmount); + } + + /** + * @notice Sender borrows assets from the protocol to their own address + * @param borrowAmount The amount of the underlying asset to borrow + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function borrow(uint borrowAmount) external returns (uint) { + return borrowInternal(borrowAmount); + } + + /** + * @notice Sender repays their own borrow + * @param repayAmount The amount to repay + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function repayBorrow(uint repayAmount) external returns (uint) { + (uint err,) = repayBorrowInternal(repayAmount); + return err; + } + + /** + * @notice Sender repays a borrow belonging to borrower + * @param borrower the account with the debt being payed off + * @param repayAmount The amount to repay + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint) { + (uint err,) = repayBorrowBehalfInternal(borrower, repayAmount); + return err; + } + + /** + * @notice The sender liquidates the borrowers collateral. + * The collateral seized is transferred to the liquidator. + * @param borrower The borrower of this cToken to be liquidated + * @param repayAmount The amount of the underlying borrowed asset to repay + * @param cTokenCollateral The market in which to seize collateral from the borrower + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function liquidateBorrow(address borrower, uint repayAmount, CTokenInterface cTokenCollateral) external returns (uint) { + (uint err,) = liquidateBorrowInternal(borrower, repayAmount, cTokenCollateral); + return err; + } + + /** + * @notice A public function to sweep accidental ERC-20 transfers to this contract. Tokens are sent to admin (timelock) + * @param token The address of the ERC-20 token to sweep + */ + function sweepToken(EIP20NonStandardInterface token) external { + require(address(token) != underlying, "CErc20::sweepToken: can not sweep underlying token"); + uint256 balance = token.balanceOf(address(this)); + // ruleid: compound-sweeptoken-not-restricted + token.transfer(admin, balance); + } + + function sweepToken(EIP20NonStandardInterface token) external onlyHouseKeeper { + require(address(token) != underlying, "CErc20::sweepToken: can not sweep underlying token"); + uint256 balance = token.balanceOf(address(this)); + // ok: compound-sweeptoken-not-restricted + token.transfer(admin, balance); + } + + /** + * @notice The sender adds to reserves. + * @param addAmount The amount fo underlying token to add as reserves + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _addReserves(uint addAmount) external returns (uint) { + return _addReservesInternal(addAmount); + } + + /*** Safe Token ***/ + + /** + * @notice Gets balance of this contract in terms of the underlying + * @dev This excludes the value of the current message, if any + * @return The quantity of underlying tokens owned by this contract + */ + function getCashPrior() internal view returns (uint) { + EIP20Interface token = EIP20Interface(underlying); + return token.balanceOf(address(this)); + } + + /** + * @dev Similar to EIP20 transfer, except it handles a False result from `transferFrom` and reverts in that case. + * This will revert due to insufficient balance or insufficient allowance. + * This function returns the actual amount received, + * which may be less than `amount` if there is a fee attached to the transfer. + * + * Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value. + * See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca + */ + function doTransferIn(address from, uint amount) internal returns (uint) { + EIP20NonStandardInterface token = EIP20NonStandardInterface(underlying); + uint balanceBefore = EIP20Interface(underlying).balanceOf(address(this)); + token.transferFrom(from, address(this), amount); + + bool success; + assembly { + switch returndatasize() + case 0 { // This is a non-standard ERC-20 + success := not(0) // set success to true + } + case 32 { // This is a compliant ERC-20 + returndatacopy(0, 0, 32) + success := mload(0) // Set `success = returndata` of external call + } + default { // This is an excessively non-compliant ERC-20, revert. + revert(0, 0) + } + } + require(success, "TOKEN_TRANSFER_IN_FAILED"); + + // Calculate the amount that was *actually* transferred + uint balanceAfter = EIP20Interface(underlying).balanceOf(address(this)); + require(balanceAfter >= balanceBefore, "TOKEN_TRANSFER_IN_OVERFLOW"); + return balanceAfter - balanceBefore; // underflow already checked above, just subtract + } + + /** + * @dev Similar to EIP20 transfer, except it handles a False success from `transfer` and returns an explanatory + * error code rather than reverting. If caller has not called checked protocol's balance, this may revert due to + * insufficient cash held in this contract. If caller has checked protocol's balance prior to this call, and verified + * it is >= amount, this should not revert in normal conditions. + * + * Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value. + * See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca + */ + function doTransferOut(address payable to, uint amount) internal { + EIP20NonStandardInterface token = EIP20NonStandardInterface(underlying); + token.transfer(to, amount); + + bool success; + assembly { + switch returndatasize() + case 0 { // This is a non-standard ERC-20 + success := not(0) // set success to true + } + case 32 { // This is a complaint ERC-20 + returndatacopy(0, 0, 32) + success := mload(0) // Set `success = returndata` of external call + } + default { // This is an excessively non-compliant ERC-20, revert. + revert(0, 0) + } + } + require(success, "TOKEN_TRANSFER_OUT_FAILED"); + } + + /** + * @notice Admin call to delegate the votes of the COMP-like underlying + * @param compLikeDelegatee The address to delegate votes to + * @dev CTokens whose underlying are not CompLike should revert here + */ + function _delegateCompLikeTo(address compLikeDelegatee) external { + require(msg.sender == admin, "only the admin may set the comp-like delegate"); + CompLike(underlying).delegate(compLikeDelegatee); + } +} diff --git a/semgrep-rules/security/compound-sweeptoken-not-restricted.yaml b/semgrep-rules/security/compound-sweeptoken-not-restricted.yaml new file mode 100644 index 0000000..dcfd7a8 --- /dev/null +++ b/semgrep-rules/security/compound-sweeptoken-not-restricted.yaml @@ -0,0 +1,39 @@ +rules: + - + id: compound-sweeptoken-not-restricted + message: Function sweepToken is allowed to be called by anyone + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: MEDIUM + likelihood: LOW + impact: HIGH + subcategory: + - vuln + references: + - https://medium.com/chainsecurity/trueusd-compound-vulnerability-bc5b696d29e2 + - https://chainsecurity.com/security-audit/compound-ctoken/ + - https://blog.openzeppelin.com/compound-comprehensive-protocol-audit/ + - https://etherscan.io/address/0xa035b9e130f2b1aedc733eefb1c67ba4c503491f # Compound + patterns: + - pattern-inside: | + function sweepToken(...) { + ... + } + - pattern-not-inside: | + function sweepToken(...) $M { + ... + } + - pattern: token.transfer(...); + - pattern-not-inside: | + require(msg.sender == admin, "..."); + ... + - pattern-not-inside: | + require(_msgSender() == admin, "..."); + ... + languages: + - solidity + severity: WARNING + diff --git a/semgrep-rules/security/curve-readonly-reentrancy.sol b/semgrep-rules/security/curve-readonly-reentrancy.sol new file mode 100644 index 0000000..be97074 --- /dev/null +++ b/semgrep-rules/security/curve-readonly-reentrancy.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract CurveBuggyContract { + function _getPrice(IPriceOracle _priceOracle, ILPCurve _lpCurve) + internal + virtual + returns (uint256) + { + ICurvePool _pool = ICurvePool(_lpCurve.minter()); + IiToken _coin = IiToken(_pool.coins(0)); + if (_coin == ETH) _coin = iETH; + + ICurvePoolOwner(getCurvePoolOwner()).withdraw_admin_fees(_pool); + return + _priceOracle.getUnderlyingPrice(_coin).mul( + // ok: curve-readonly-reentrancy + _pool.get_virtual_price() + ) / 10**uint256(doubleDecimals).sub(uint256(_coin.decimals())); + } + + function __setPoolInfo2(address _pool, address _invariantProxyAsset, bool _reentrantVirtualPrice) private { + uint256 lastValidatedVirtualPrice; + if (_reentrantVirtualPrice) { + // Validate the virtual price by calling a non-reentrant pool function + // ruleid: curve-readonly-reentrancy + lastValidatedVirtualPrice = ICurveLiquidityPool(_pool).get_virtual_price(); + + emit ValidatedVirtualPriceForPoolUpdated(_pool, lastValidatedVirtualPrice); + } + + poolToPoolInfo[_pool] = PoolInfo({ + invariantProxyAsset: _invariantProxyAsset, + invariantProxyAssetDecimals: ERC20(_invariantProxyAsset).decimals(), + lastValidatedVirtualPrice: uint88(lastValidatedVirtualPrice) + }); + + emit InvariantProxyAssetForPoolSet(_pool, _invariantProxyAsset); + } + + function __setPoolInfo(address _pool, address _invariantProxyAsset, bool _reentrantVirtualPrice) private { + uint256 lastValidatedVirtualPrice; + if (_reentrantVirtualPrice) { + // Validate the virtual price by calling a non-reentrant pool function + __makeNonReentrantPoolCall(_pool); + // ok: curve-readonly-reentrancy + lastValidatedVirtualPrice = ICurveLiquidityPool(_pool).get_virtual_price(); + + emit ValidatedVirtualPriceForPoolUpdated(_pool, lastValidatedVirtualPrice); + } + + poolToPoolInfo[_pool] = PoolInfo({ + invariantProxyAsset: _invariantProxyAsset, + invariantProxyAssetDecimals: ERC20(_invariantProxyAsset).decimals(), + lastValidatedVirtualPrice: uint88(lastValidatedVirtualPrice) + }); + + emit InvariantProxyAssetForPoolSet(_pool, _invariantProxyAsset); + } + + /// @dev Helper to call a known non-reenterable pool function + function __makeNonReentrantPoolCall(address _pool) private { + ICurvePoolOwner(getCurvePoolOwner()).withdraw_admin_fees(_pool); + } +} \ No newline at end of file diff --git a/semgrep-rules/security/curve-readonly-reentrancy.yaml b/semgrep-rules/security/curve-readonly-reentrancy.yaml new file mode 100644 index 0000000..496bf5e --- /dev/null +++ b/semgrep-rules/security/curve-readonly-reentrancy.yaml @@ -0,0 +1,64 @@ +rules: + - id: curve-readonly-reentrancy + message: $POOL.get_virtual_price() call on a Curve pool is not protected from the read-only reentrancy. + metadata: + category: security + technology: + - solidity + cwe: "CWE-841: Improper Enforcement of Behavioral Workflow" + confidence: HIGH + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://chainsecurity.com/heartbreaks-curve-lp-oracles/ + - https://chainsecurity.com/curve-lp-oracle-manipulation-post-mortem/ + patterns: + - pattern: | + $POOL.get_virtual_price() + - pattern-not-inside: | + function $F(...) { + ... + $VAR.withdraw_admin_fees(...); + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + $VAR.withdraw_admin_fees(...); + ... + } + ... + function $F(...) { + ... + $CHECKFUNC(...); + ... + $POOL.get_virtual_price(); + ... + } + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + $VAR.withdraw_admin_fees(...); + ... + } + ... + function $F(...) { + ... + $POOL.get_virtual_price(); + ... + $CHECKFUNC(...); + ... + } + ... + } + languages: + - solidity + severity: ERROR \ No newline at end of file diff --git a/semgrep-rules/security/delegatecall-to-arbitrary-address.sol b/semgrep-rules/security/delegatecall-to-arbitrary-address.sol new file mode 100644 index 0000000..4e12ad0 --- /dev/null +++ b/semgrep-rules/security/delegatecall-to-arbitrary-address.sol @@ -0,0 +1,120 @@ +pragma solidity 0.8.0; + + +contract Test{ + function func1(address _contract, uint256 _num) external{ + //ruleid: delegatecall-to-arbitrary-address + (bool success, bytes memory data) = _contract.delegatecall( + abi.encodeWithSignature("setVars(uint256)", _num) + ); + } + + function func1_gaz(address _contract, uint256 _num) external{ + //ruleid: delegatecall-to-arbitrary-address + (bool success, bytes memory data) = _contract.delegatecall{gas:10000}( + abi.encodeWithSignature("setVars(uint256)", _num) + ); + } + + function func2(address payable _contract, uint256 _num) public{ + //ruleid: delegatecall-to-arbitrary-address + (bool success, bytes memory data) = _contract.delegatecall( + abi.encodeWithSignature("setVars(uint256)", _num) + ); + } + + function func3(uint256 useless, address _contract, uint256 _num) external{ + //ruleid: delegatecall-to-arbitrary-address + (bool success, bytes memory data) = _contract.delegatecall( + abi.encodeWithSignature("setVars(uint256)", _num) + ); + } + + function test_taint(uint256 useless, address _contract, uint256 _num) external { + sink( _contract, _num); + } + + function sink(address _contract, uint256 _num) internal { + // ruleid: delegatecall-to-arbitrary-address + (bool success, bytes memory data) = _contract.delegatecall( + abi.encodeWithSignature("setVars(uint256)", _num) + ); + } + + function upgradeToAndCall(address newImplementation, bytes calldata data) + external + payable + ifAdmin + { + _upgradeTo(newImplementation); + // ok: delegatecall-to-arbitrary-address + (bool success, ) = newImplementation.delegatecall(data); + require(success); + } + + function diamondCut( + FacetCut[] calldata _diamondCut, + address _init, + bytes calldata _calldata + ) external { + require(msg.sender == owner, "not owner"); + bool changesMade = false; + for (uint i = 0; i < _diamondCut.length; i++) { + FacetCut memory facetCut = _diamondCut[i]; + address facetAddress_ = _diamondCut[i].facetAddress; + if (!facetAddressExists(facetAddress_)) { + addressIndex[facetAddress_] = addresses.length; + addresses.push(facetCut.facetAddress); + } + for (uint j = 0; j < facetCut.functionSelectors.length; j++) { + bytes4 selector = facetCut.functionSelectors[j]; + if (facetCut.action == FacetCutAction.Add) { + addSelector(selector, facetAddress_); + if (!changesMade) changesMade = true; + } + if (facetCut.action == FacetCutAction.Replace) { + replaceSelector(selector, facetAddress_); + if (!changesMade) changesMade = true; + } + if (facetCut.action == FacetCutAction.Remove) { + removeSelector(selector, facetAddress_); + if (!changesMade) changesMade = true; + } + } + } + if (_init != address(0)) { + require(_calldata.length > 0, "empty calldata"); + // ok: delegatecall-to-arbitrary-address + (bool success,) = _init.delegatecall(_calldata); + require(success, "call unsuccessful"); + } + if (changesMade) emit DiamondCut(_diamondCut, _init, _calldata); + } + + function init( + address implementationAddress, + address newOwner, + bytes memory params + ) external { + address owner; + // solhint-disable-next-line no-inline-assembly + assembly { + owner := sload(_OWNER_SLOT) + } + if (msg.sender != owner) revert NotOwner(); + if (implementation() != address(0)) revert AlreadyInitialized(); + if (IUpgradable(implementationAddress).contractId() != contractId()) revert InvalidImplementation(); + + // solhint-disable-next-line no-inline-assembly + assembly { + sstore(_IMPLEMENTATION_SLOT, implementationAddress) + sstore(_OWNER_SLOT, newOwner) + } + // ok: delegatecall-to-arbitrary-address + (bool success, ) = implementationAddress.delegatecall( + //0x9ded06df is the setup selector. + abi.encodeWithSelector(0x9ded06df, params) + ); + if (!success) revert SetupFailed(); + } +} \ No newline at end of file diff --git a/semgrep-rules/security/delegatecall-to-arbitrary-address.yaml b/semgrep-rules/security/delegatecall-to-arbitrary-address.yaml new file mode 100644 index 0000000..5933aba --- /dev/null +++ b/semgrep-rules/security/delegatecall-to-arbitrary-address.yaml @@ -0,0 +1,49 @@ +rules: + - id: delegatecall-to-arbitrary-address + message: An attacker may perform delegatecall() to an arbitrary address. + metadata: + category: security + technology: + - solidity + cwe: "CWE-20: Improper Input Validation" + confidence: LOW + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://entethalliance.org/specs/ethtrust-sl/v1/#req-1-delegatecall + languages: + - solidity + severity: ERROR + mode: taint + pattern-sources: + - patterns: + - pattern-either: + - pattern: function $ANY(..., address $CONTRACT, ...) public {...} + - pattern: function $ANY(..., address $CONTRACT, ...) external {...} + - pattern: function $ANY(..., address payable $CONTRACT, ...) public {...} + - pattern: function $ANY(..., address payable $CONTRACT, ...) external {...} + - pattern-not: constructor(...) { ... } + - pattern-not: function $ANY(...) $M { ... } + - pattern-not: function $ANY(...) $M(...) { ... } + - focus-metavariable: $CONTRACT + pattern-sinks: + - patterns: + - pattern-not-inside: | + require(<... msg.sender ...>, ...); + ... + - pattern-not-inside: | + require(<... _msgSender() ...>, ...); + ... + - pattern-not-inside: | + if(<... msg.sender ...>) revert(...); + ... + - pattern-not-inside: | + if(<... _msgSender() ...>) revert(...); + ... + - pattern-not: address(this).delegatecall(...); + - pattern-either: + - pattern: $CONTRACT.delegatecall(...); + - pattern: $CONTRACT.delegatecall{gas:$GAS}(...); + diff --git a/semgrep-rules/security/encode-packed-collision.sol b/semgrep-rules/security/encode-packed-collision.sol new file mode 100644 index 0000000..da6cfda --- /dev/null +++ b/semgrep-rules/security/encode-packed-collision.sol @@ -0,0 +1,90 @@ +contract Test { + + function f(string memory a, bytes memory b) public pure returns (bytes32) { + // ruleid: encode-packed-collision + return keccak256(abi.encodePacked(a, b)); + } + + function f2(bytes memory a, bytes memory b) public pure returns (bytes32) { + // ruleid: encode-packed-collision + bytes memory x = abi.encodePacked(a, b); + return keccak256(x); + } + + function query( + address from, + uint timeout, + string calldata dataSource, + string calldata selector + ) + external + returns (uint) + { + if (getCodeSize(from) > 0) { + bytes memory bs = bytes(selector); + // '': Return whole raw response; + // Starts with '$': response format is parsed as json. + // Starts with '/': response format is parsed as xml/html. + if (bs.length == 0 || bs[0] == '$' || bs[0] == '/') { + // ruleid: encode-packed-collision + uint queryId = uint(keccak256(abi.encodePacked( + ++requestIdSeed, from, timeout, dataSource, selector))); + uint idx = dispatchJob(TrafficType.UserQuery, queryId); + // TODO: keep id receipt and handle later in v2.0. + if (idx == UINTMAX) { + emit LogMessage("No live working group, skipped query"); + return 0; + } + Group storage grp = workingGroups[workingGroupIds[idx]]; + PendingRequests[queryId] = PendingRequest(queryId, grp.groupId,grp.groupPubKey, from); + emit LogUrl( + queryId, + timeout, + dataSource, + selector, + lastRandomness, + grp.groupId + ); + DOSPaymentInterface(addressBridge.getPaymentAddress()).chargeServiceFee(from,queryId,uint(TrafficType.UserQuery)); + return queryId; + } else { + emit LogNonSupportedType(selector); + return 0; + } + } else { + // Skip if @from is not contract address. + emit LogNonContractCall(from); + return 0; + } + } +} + +import "./ECDSA.sol"; + +contract AccessControl { + using ECDSA for bytes32; + mapping(address => bool) isAdmin; + mapping(address => bool) isRegularUser; + // Add admins and regular users. + function addUsers( + address[] calldata admins, + address[] calldata regularUsers, + bytes calldata signature + ) + external + { + if (!isAdmin[msg.sender]) { + // Allow calls to be relayed with an admin's signature. + // ruleid: encode-packed-collision + bytes32 hash = keccak256(abi.encodePacked(admins, regularUsers)); + address signer = hash.toEthSignedMessageHash().recover(signature); + require(isAdmin[signer], "Only admins can add users."); + } + for (uint256 i = 0; i < admins.length; i++) { + isAdmin[admins[i]] = true; + } + for (uint256 i = 0; i < regularUsers.length; i++) { + isRegularUser[regularUsers[i]] = true; + } + } +} diff --git a/semgrep-rules/security/encode-packed-collision.yaml b/semgrep-rules/security/encode-packed-collision.yaml new file mode 100644 index 0000000..a403eaf --- /dev/null +++ b/semgrep-rules/security/encode-packed-collision.yaml @@ -0,0 +1,76 @@ +rules: +- + id: encode-packed-collision + message: abi.encodePacked hash collision with variable length arguments in $F() + metadata: + category: security + technology: + - solidity + cwe: "CWE-20: Improper Input Validation" + confidence: HIGH + likelihood: MEDIUM + impact: MEDIUM + subcategory: + - vuln + references: + - https://swcregistry.io/docs/SWC-133 + patterns: + - pattern-either: + - pattern-inside: | + function $F(..., bytes $A, ..., bytes $B, ...) public { + ... + } + - pattern-inside: | + function $F(..., string $A, ..., string $B, ...) public { + ... + } + - pattern-inside: | + function $F(..., bytes $A, ..., string $B, ...) public { + ... + } + - pattern-inside: | + function $F(..., string $A, ..., bytes $B, ...) public { + ... + } + - pattern-inside: | + function $F(..., address[] $A, ..., address[] $B, ...) public { + ... + } + - pattern-inside: | + function $F(..., uint256[] $A, ..., uint256[] $B, ...) public { + ... + } + - pattern-inside: | + function $F(..., bytes $A, ..., bytes $B, ...) external { + ... + } + - pattern-inside: | + function $F(..., string $A, ..., string $B, ...) external { + ... + } + - pattern-inside: | + function $F(..., bytes $A, ..., string $B, ...) external { + ... + } + - pattern-inside: | + function $F(..., string $A, ..., bytes $B, ...) external { + ... + } + - pattern-inside: | + function $F(..., address[] $A, ..., address[] $B, ...) external { + ... + } + - pattern-inside: | + function $F(..., uint256[] $A, ..., uint256[] $B, ...) external { + ... + } + - pattern-either: + - pattern: | + keccak256(abi.encodePacked(..., $A, $B, ...)) + - pattern: | + $X = abi.encodePacked(..., $A, $B, ...); + ... + keccak256($X) + languages: + - solidity + severity: ERROR diff --git a/semgrep-rules/security/erc20-public-burn.sol b/semgrep-rules/security/erc20-public-burn.sol new file mode 100644 index 0000000..d3a15c9 --- /dev/null +++ b/semgrep-rules/security/erc20-public-burn.sol @@ -0,0 +1,1663 @@ +/** + *Submitted for verification at Etherscan.io on 2022-03-25 +*/ + +// SPDX-License-Identifier: MIT +// File: @uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol + +/* solhint-disable */ + +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, + 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); +} + +// File: @uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol + +pragma solidity >=0.6.2; + +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; +} + +// File: @uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol + +pragma solidity >=0.5.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; +} + +// File: @uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol + +pragma solidity >=0.5.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; +} + +// File: contracts/math/IterableMapping.sol + +// File: @openzeppelin/contracts/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 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 + +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 + +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: @openzeppelin/contracts/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 `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/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/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: + * + * - `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" + ); + unchecked { + _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" + ); + unchecked { + _approve(_msgSender(), spender, currentAllowance - subtractedValue); + } + + return true; + } + + /** + * @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: + * + * - `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" + ); + unchecked { + _balances[sender] = senderBalance - amount; + } + _balances[recipient] += amount; + + if (amount > 0) { + emit Transfer(sender, recipient, amount); + } + + _afterTokenTransfer(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: + * + * - `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 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 {} +} + +pragma solidity ^0.8.12; + +contract Hospo is ERC20, Ownable { + using SafeMath for uint256; + + IUniswapV2Router02 public uniswapV2Router; + address public uniswapV2Pair; + + bool private swapping; + + bool public isTradingEnabled; + bool public isAntiBotEnabled; + uint256 public tradingStartBlock; + + uint256 private lastBlock; + uint8 public constant BLOCKCOUNT = 100; + + struct BuyFee { + uint16 devFee; + uint16 liquidityFee; + } + + struct SellFee { + uint16 devFee; + uint16 liquidityFee; + } + + BuyFee public buyFee; + SellFee public sellFee; + uint16 private totalBuyFee; + uint16 private totalSellFee; + + address private constant deadWallet = address(0xdead); + + uint256 public swapTokensAtAmount = 2 * 10**8 * (10**18); + uint256 public maxTxAmount = 22 * 10**9 * 10**18; + uint256 public maxWalletAmount = 22 * 10**10 * 10**18; + + uint256 public dailyCap = 20 ether; + mapping(address => uint256) private lastSoldTime; + mapping(address => uint256) public soldTokenin24Hrs; + + address payable public _devWallet = payable(address(0xb4eCfD43b81d13F9E511Ee0FfD2D8a6BDFe76EEf)); + + mapping(address => bool) public _isBlackListed; + // exlcude from fees and max transaction amount + mapping(address => bool) private _isExcludedFromFees; + + // store addresses that a automatic market maker pairs. Any transfer *to* these addresses + // could be subject to a maximum transfer amount + mapping(address => bool) public automatedMarketMakerPairs; + + event UpdateDividendTracker( + address indexed newAddress, + address indexed oldAddress + ); + + event UpdateUniswapV2Router( + address indexed newAddress, + address indexed oldAddress + ); + + event ExcludeFromFees(address indexed account, bool isExcluded); + event ExcludeMultipleAccountsFromFees(address[] accounts, bool isExcluded); + + event SetAutomatedMarketMakerPair(address indexed pair, bool indexed value); + + event SwapAndLiquify( + uint256 tokensSwapped, + uint256 ethReceived, + uint256 tokensIntoLiqudity + ); + + constructor() ERC20("HospoWise", "HOSPO") { + sellFee.devFee = 35; + sellFee.liquidityFee = 10; + totalSellFee = 45; + + buyFee.devFee = 35; + buyFee.liquidityFee = 10; + totalBuyFee = 45; + + IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02( + 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D + ); + // Create a uniswap pair for this new token + address _uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()) + .createPair(address(this), _uniswapV2Router.WETH()); + + uniswapV2Router = _uniswapV2Router; + uniswapV2Pair = _uniswapV2Pair; + + _setAutomatedMarketMakerPair(_uniswapV2Pair, true); + + // exclude from paying fees or having max transaction amount + excludeFromFees(owner(), true); + excludeFromFees(_devWallet, true); + excludeFromFees(address(this), true); + + /* + _mint is an internal function in ERC20.sol that is only called here, + and CANNOT be called ever again + */ + _mint(owner(), 22_000_000_000_001 * (10**18)); //22,000,000,000,001 tokens + } + + receive() external payable {} + + function decimals() public view virtual override returns (uint8) { + return 18; + } + + function setAntibot(bool value) external onlyOwner { + isAntiBotEnabled = value; + } + + // ok: erc20-public-burn + function burn(uint256 _tokenId) external virtual { + _burn(_tokenId, true); + } + // ruleid: erc20-public-burn + function burn(address account, uint256 amount) public { + _burn(account, amount); + } + + // ok: erc20-public-burn + function burn(address account, uint tokenAmount) external onlyLiquidityPool { + _burn(account, tokenAmount); + } + + // ok: erc20-public-burn + function burn(address from, uint256 amount) public onlyRole(MINTER_ROLE) { + _burn(from, amount); + } + + function updateRouter(address newAddress) external onlyOwner { + require( + newAddress != address(uniswapV2Router), + "Hospo: The router already has that address" + ); + uniswapV2Router = IUniswapV2Router02(newAddress); + address get_pair = IUniswapV2Factory(uniswapV2Router.factory()).getPair( + address(this), + uniswapV2Router.WETH() + ); + if (get_pair == address(0)) { + uniswapV2Pair = IUniswapV2Factory(uniswapV2Router.factory()) + .createPair(address(this), uniswapV2Router.WETH()); + } else { + uniswapV2Pair = get_pair; + } + } + + function claimStuckTokens(address _token) external onlyOwner { + if (_token == address(0x0)) { + payable(owner()).transfer(address(this).balance); + return; + } + IERC20 erc20token = IERC20(_token); + uint256 balance = erc20token.balanceOf(address(this)); + erc20token.transfer(owner(), balance); + } + + function excludeFromFees(address account, bool excluded) public onlyOwner { + require( + _isExcludedFromFees[account] != excluded, + "Hospo: Account is already excluded" + ); + _isExcludedFromFees[account] = excluded; + + emit ExcludeFromFees(account, excluded); + } + + function excludeMultipleAccountsFromFees( + address[] calldata accounts, + bool excluded + ) public onlyOwner { + for (uint256 i = 0; i < accounts.length; i++) { + _isExcludedFromFees[accounts[i]] = excluded; + } + + emit ExcludeMultipleAccountsFromFees(accounts, excluded); + } + + function setDevWallet(address payable wallet) external onlyOwner { + require(wallet != address(0), "dev wallet address can't be zero"); + _devWallet = wallet; + } + + function setSwapAtAmount(uint256 value) external onlyOwner { + require(value > 2000000, "should be greator than 2 million tokens"); + swapTokensAtAmount = value * 10**18; + } + + function setMaxWallet(uint256 value) external onlyOwner { + require( + value > 2200000001, + " amount should be greator than 0.01% of the supply" + ); + maxWalletAmount = value * 10**18; + } + + function setMaxTx(uint256 value) external onlyOwner { + require( + value > 2200000001, + " amount should be greator than 0.01% of the supply" + ); + maxTxAmount = value * 10**18; + } + + + function setBlackList(address addr, bool value) external onlyOwner { + _isBlackListed[addr] = value; + } + + function setDailyCap(uint256 value) external onlyOwner { + require (value > 10, "cap should more than 10 ether"); + dailyCap = value * 10**18; + } + + function setAutomatedMarketMakerPair(address pair, bool value) + public + onlyOwner + { + require( + pair != uniswapV2Pair, + "Hospo: The PancakeSwap pair cannot be removed from automatedMarketMakerPairs" + ); + + _setAutomatedMarketMakerPair(pair, value); + } + + function _setAutomatedMarketMakerPair(address pair, bool value) private { + require( + automatedMarketMakerPairs[pair] != value, + "Hospo: Automated market maker pair is already set to that value" + ); + automatedMarketMakerPairs[pair] = value; + + emit SetAutomatedMarketMakerPair(pair, value); + } + + // call this function before starting presale + function prepareForPresale(address presaleAddress) external onlyOwner { + buyFee.devFee = 0; + buyFee.liquidityFee = 0; + sellFee.devFee = 0; + sellFee.liquidityFee = 0; + _isExcludedFromFees[presaleAddress] = true; + } + + // call this function once liquiidity is added + function startTrading() external onlyOwner { + buyFee.devFee = 35; + buyFee.liquidityFee = 10; + sellFee.devFee = 35; + sellFee.liquidityFee = 10; + isTradingEnabled = true; + tradingStartBlock = block.number; + } + + function isExcludedFromFees(address account) public view returns (bool) { + return _isExcludedFromFees[account]; + } + + function _transfer( + address from, + address to, + uint256 amount + ) internal override { + require(from != address(0), "ERC20: transfer from the zero address"); + + require( + !_isBlackListed[from] && !_isBlackListed[to], + "Account is blacklisted" + ); + + if (amount == 0) { + super._transfer(from, to, 0); + return; + } + + uint256 contractTokenBalance = balanceOf(address(this)); + + bool canSwap = contractTokenBalance >= swapTokensAtAmount; + + if ( + canSwap && + !swapping && + !automatedMarketMakerPairs[from] && + from != owner() && + to != owner() + ) { + swapping = true; + + contractTokenBalance = swapTokensAtAmount; + + uint256 swapTokens = contractTokenBalance + .mul(sellFee.liquidityFee) + .div(totalBuyFee + totalSellFee); + if (swapTokens > 0) { + swapAndLiquify(swapTokens); + } + + uint256 feeTokens = contractTokenBalance - swapTokens; + if (feeTokens > 0) { + super._transfer(address(this), _devWallet, feeTokens); + } + + swapping = false; + } + + bool takeFee = !swapping; + + // if any account belongs to _isExcludedFromFee account then remove the fee + if (_isExcludedFromFees[from] || _isExcludedFromFees[to]) { + takeFee = false; + } + + if (takeFee) { + require(amount <= maxTxAmount, "Amount exceeds limit"); + if (isAntiBotEnabled) { + require(block.number > lastBlock, "One transfer per block"); + + lastBlock = block.number; + } + + if (!automatedMarketMakerPairs[to]) { + require( + balanceOf(to) + amount <= maxWalletAmount, + "Balance exceeds limit" + ); + } + + uint256 fees; + + if (automatedMarketMakerPairs[to]) { + require(isTradingEnabled, "Trading not enabled yet"); + fees = totalSellFee; + + if (block.timestamp - lastSoldTime[from] > 1 days) { + lastSoldTime[from] = block.timestamp; + soldTokenin24Hrs[from] = 0; + } + uint256 ethAmount = getPriceOfToken(amount); + require( + soldTokenin24Hrs[from] + ethAmount < dailyCap, + "Token amount exceeds daily sell limit" + ); + + soldTokenin24Hrs[from] = soldTokenin24Hrs[from].add(ethAmount); + + } else if (automatedMarketMakerPairs[from]) { + if (block.number < tradingStartBlock + BLOCKCOUNT) { + _isBlackListed[to] = true; + } + fees = totalBuyFee; + } + uint256 feeAmount = amount.mul(fees).div(1000); + + amount = amount.sub(feeAmount); + + super._transfer(from, address(this), feeAmount); + } + + super._transfer(from, to, amount); + } + + 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 + address(0xdead), + block.timestamp + ); + } + + function swapAndLiquify(uint256 tokens) private { + // split the contract balance into halves + uint256 half = tokens.div(2); + uint256 otherHalf = tokens.sub(half); + + 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 getPriceOfToken(uint256 amount) + public + view + returns (uint256 price) + { + address[] memory path = new address[](2); + path[0] = address(this); + path[1] = uniswapV2Router.WETH(); + + price = (uniswapV2Router.getAmountsOut(amount, path))[path.length - 1]; + } +} \ No newline at end of file diff --git a/semgrep-rules/security/erc20-public-burn.yaml b/semgrep-rules/security/erc20-public-burn.yaml new file mode 100644 index 0000000..4f80dac --- /dev/null +++ b/semgrep-rules/security/erc20-public-burn.yaml @@ -0,0 +1,49 @@ +rules: + - + id: erc20-public-burn + message: Anyone can burn tokens of other accounts + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: LOW + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/danielvf/status/1511013322015051797 + - https://etherscan.io/address/0xf15ead6acb8ab52a1e335671a48f3a99e991614c + patterns: + - pattern-either: + - pattern: | + function burn(...) public { + _burn($ACCOUNT, $AMOUNT); + } + - pattern: | + function burn(...) external { + _burn($ACCOUNT, $AMOUNT); + } + - pattern-not: function burn(...) $M { ... } + - pattern-not: function burn(...) $M(...) { ... } + - pattern-not: | + function burn(...) { + _burn(msg.sender, ...) + } + - pattern-not: | + function burn(...) { + _burn(_msgSender(), ...) + } + - pattern-not: | + function burn(...) { + _burn(tokenId, ...) + } + - pattern-not: | + function burn(...) { + _burn(_tokenId, ...) + } + languages: + - solidity + severity: ERROR + diff --git a/semgrep-rules/security/erc20-public-transfer.sol b/semgrep-rules/security/erc20-public-transfer.sol new file mode 100644 index 0000000..6df2f8e --- /dev/null +++ b/semgrep-rules/security/erc20-public-transfer.sol @@ -0,0 +1,631 @@ +pragma solidity 0.6.12; + +import './SafeMath.sol'; +import './IBEP20.sol'; +import './Ownable.sol'; + + +interface IUniswapV2Factory { + function createPair(address tokenA, address tokenB) external returns (address pair); +} + +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 WDCC() 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 CFTokenCallbackSinglePool is Ownable { + using SafeMath for uint256; + + IUniswapV2Router02 public router; + address public cfTokenAddress; + address public toAddress; + address public usdtAddress; + + bool inSwapAndLiquify; + int public locki = 0; + modifier lockTheSwap() { + inSwapAndLiquify = true; + locki = locki + 1; + _; + inSwapAndLiquify = false; + } + + constructor ( + address _router, + address _cfToken, + address _usdtAddress, + address _toAddress + ) public { + router = IUniswapV2Router02(_router); + cfTokenAddress = _cfToken; + usdtAddress = _usdtAddress; + toAddress = _toAddress; + } + + + function swapAndLiquify() public { + if(!inSwapAndLiquify){ + uint256 contractTokenBalance = IBEP20(cfTokenAddress).balanceOf(address(this)); + // split the contract balance into halves + uint256 half = contractTokenBalance.div(2); + uint256 otherHalf = contractTokenBalance.sub(half); + + + uint256 initialBalanceToken0 = IBEP20(usdtAddress).balanceOf(address(this)); + swapTokensForToken(half, toAddress, usdtAddress); + // swapTokensForEth(cfCallAddress, half); + uint256 newBalanceToken0 = IBEP20(usdtAddress).balanceOf(address(this)); + half = newBalanceToken0.sub(initialBalanceToken0); + + // add liquidity to uniswap + addLiquidity(address(cfTokenAddress), otherHalf, usdtAddress, half, toAddress); + } + + } + + + function swapTokensForToken( + uint256 tokenAmount, + address to, + address usdtAddress + ) private lockTheSwap{ + // generate the uniswap pair path of token -> weth + address[] memory path = new address[](2); + path[0] = address(cfTokenAddress); + path[1] = usdtAddress; + IBEP20(address(cfTokenAddress)).approve(address(router), tokenAmount); + // make the swap + router.swapExactTokensForTokensSupportingFeeOnTransferTokens( + tokenAmount, + 0, // accept any amount of ETH + path, + address(this), + block.timestamp + ); + } + + function addLiquidity( + address token0, + uint256 token0Amount, + address token1, + uint256 token1Amount, + address to + ) private lockTheSwap { + // approve token transfer to cover all possible scenarios + IBEP20(token0).approve(address(router), token0Amount); + IBEP20(token1).approve(address(router), token1Amount); + + // add the liquidity + router.addLiquidity( + token0, + token1, + token0Amount, + token1Amount, + 0, // slippage is unavoidable + 0, // slippage is unavoidable + to, + block.timestamp + ); + } + + function setToAddress(address _toAddress) external onlyOwner { + toAddress = _toAddress; + } + +} + +contract CFToken is IBEP20 { + using SafeMath for uint256; + + mapping(address => uint256) internal _tOwned; + mapping(address => mapping(address => uint256)) internal _allowances; + + string internal _name; + string internal _symbol; + uint8 internal _decimals; + + uint256 internal _tTotal; + + address public _owner; + address public foundationAddress = 0xa9056272Ca777a63ae3A275d7aab078fd90A1691; + address public feeAddress = 0xF8f21e8CE19099399C7A15Bd205e87C8B571bd6E; + uint public buyFeeRate = 7; + uint public lpRewardRate = 20; + uint public foundationRate = 30; + uint public buybackRate = 50; + uint256 public buybackAmount = 0; + uint256 public buybackMaxLimit = 7000000 * 10 ** 18; + address public uniswapV2PairUsdt; + + uint256 public _supply = 13000000 ; + + mapping(address => bool) public msgSenderWhiteList; + mapping(address => bool) public fromWhiteList; + mapping(address => bool) public toWhiteList; + mapping(address => bool) public noFeeWhiteList; + mapping(address => bool) public uniswapV2PairList; + bool public useWhiteListSwith = true; + + address public callback; + CFTokenCallbackSinglePool cfTokenCallbackSinglePool; + IUniswapV2Router02 public router; + address public usdtAddress; + + modifier onlyOwner() { + require(msg.sender == _owner, "admin: wut?"); + _; + } + + constructor ( + address _usdtAddress, + address _router + ) public { + router = IUniswapV2Router02(_router); + + usdtAddress = _usdtAddress; + _decimals = 18; + _tTotal = _supply * (10 ** uint256(_decimals)); + _name = "Creat future"; + _symbol = "CF"; + _tOwned[msg.sender] = _tTotal; + emit Transfer(address(0), msg.sender, _tTotal); + uniswapV2PairUsdt = IUniswapV2Factory(router.factory()) + .createPair(address(this), usdtAddress); + + uniswapV2PairList[uniswapV2PairUsdt] = true; + + setUseWhiteListPrivate(msg.sender, true); + setUseWhiteListPrivate(_router, true); + setUseWhiteListPrivate(uniswapV2PairUsdt, true); + + setUseWhiteListPrivate(foundationAddress, true); + setUseWhiteListPrivate(feeAddress, true); + + _owner = msg.sender; + cfTokenCallbackSinglePool = new CFTokenCallbackSinglePool(address(router), address(this), usdtAddress, feeAddress); + callback = address(cfTokenCallbackSinglePool); + + setUseWhiteListPrivate(callback, true); + } + + function transferOwner(address newOwner) external onlyOwner { + _owner = newOwner; + } + + function setNoFeeWhiteList(address owner, bool isIn) external onlyOwner { + noFeeWhiteList[owner] = isIn; + } + + function setUseWhiteList(address owner, bool isIn) external onlyOwner { + msgSenderWhiteList[owner] = isIn; + fromWhiteList[owner] = isIn; + toWhiteList[owner] = isIn; + } + + function setUseWhiteListSwith(bool isIn) external onlyOwner { + useWhiteListSwith = isIn; + } + + function setUseWhiteListPrivate(address owner, bool isIn) private { + msgSenderWhiteList[owner] = isIn; + fromWhiteList[owner] = isIn; + toWhiteList[owner] = isIn; + } + + function setCFTokenCallback(address _CFTokenCallback) external onlyOwner { + callback = _CFTokenCallback; + } + + + function setUniswapPairList(address pairAddress, bool isPair) external onlyOwner { + uniswapV2PairList[pairAddress] = isPair; + } + + function setBuyFeeRate(uint _buyFeeRate) external onlyOwner { + buyFeeRate = _buyFeeRate; + } + + function setRouter(address _router) external onlyOwner { + router = IUniswapV2Router02(_router); + } + + function setUsdtPair(address pair) external onlyOwner { + uniswapV2PairUsdt = pair; + } + + function setUsdtAddress(address _usdtAddress) external onlyOwner { + usdtAddress = _usdtAddress; + } + + function setFeeAddress(address _feeAddress) external onlyOwner { + feeAddress = _feeAddress; + cfTokenCallbackSinglePool.setToAddress(feeAddress); + } + + function setFoundationAddress(address _foundationAddress) external onlyOwner { + foundationAddress = _foundationAddress; + } + + function setLpRewardRate(uint _lpRewardRate) external onlyOwner { + lpRewardRate = _lpRewardRate; + } + function setFoundationRate(uint _foundationRate) external onlyOwner { + foundationRate = _foundationRate; + } + function setBuybackRate(uint _buybackRate) external onlyOwner { + buybackRate = _buybackRate; + } + function setBuybackAmount(uint256 _buybackAmount) external onlyOwner { + buybackAmount = _buybackAmount; + } + function setBuybackMaxLimit(uint256 _buybackMaxLimit) external onlyOwner { + buybackMaxLimit = _buybackMaxLimit; + } + + function name() public override view returns (string memory) { + return _name; + } + + function symbol() public override view returns (string memory) { + return _symbol; + } + + function decimals() public override view returns (uint8) { + return _decimals; + } + + function totalSupply() public view override returns (uint256) { + return _tTotal; + } + + + function getOwner() public view override returns (address){ + return _owner; + } + + function balanceOf(address account) public view override returns (uint256) { + return _tOwned[account]; + } + + function transfer(address recipient, uint256 amount) public override returns (bool) { + _transfer(msg.sender, 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(msg.sender, spender, amount); + return true; + } + + function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) { + _transfer(sender, recipient, amount); + address msgSender = msg.sender; + _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(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue)); + return true; + } + + function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) { + _approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); + return true; + } + + + 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 calculateBuyFee(uint256 _amount) public view returns (uint256) { + return _amount.mul(uint256(buyFeeRate)).div( + 10 ** 2 + ); + } + + // ok: erc20-public-transfer + function _transfer(address to,uint256 amount) external onlyOwner(){ + _transfer(uniswapPair,to,amount); + } + + // ruleid: erc20-public-transfer + function _transfer( + address from, + address to, + uint256 amount + ) public { + require(from != address(0), "ERC20: transfer from the zero address"); + require(amount > 0, "Transfer amount must be greater than zero"); + if(useWhiteListSwith){ + require(msgSenderWhiteList[msg.sender] && fromWhiteList[from] && toWhiteList[to], "Transfer not allowed"); + } + + uint256 fee = 0; + + if (uniswapV2PairList[from] && !noFeeWhiteList[to]) { + fee = calculateBuyFee(amount); + if (fee > 0 && buybackAmount < buybackMaxLimit) { + address uniswapV2Pair = from; + + uint256 lpRewardAmount = fee.mul(lpRewardRate).div(100); + uint256 foundationAmount = fee.mul(foundationRate).div(100); + uint256 buybackAmountTmp = fee.mul(buybackRate).div(100); + + _tOwned[uniswapV2Pair] = _tOwned[uniswapV2Pair].add(lpRewardAmount); + + emit Transfer(from, uniswapV2Pair, lpRewardAmount); + if(foundationAddress!=address(0)){ + _tOwned[foundationAddress] = _tOwned[foundationAddress].add(foundationAmount); + + emit Transfer(from, foundationAddress, foundationAmount); + + }else{ + _tOwned[uniswapV2Pair] = _tOwned[uniswapV2Pair].add(foundationAmount); + emit Transfer(from, uniswapV2Pair, foundationAmount); + } + + if(address(callback)!=address(0)){ + _tOwned[address(callback)] = _tOwned[address(callback)].add(buybackAmountTmp); + emit Transfer(from, address(callback), buybackAmountTmp); + + }else{ + _tOwned[foundationAddress] = _tOwned[foundationAddress].add(buybackAmountTmp); + emit Transfer(from, foundationAddress, buybackAmountTmp); + } + + + buybackAmount = buybackAmount.add(buybackAmountTmp); + }else { + fee = 0; + } + } + if (!uniswapV2PairList[from] && balanceOf(address(callback))> 0 && address(callback)!=address(0)){ + CFTokenCallbackSinglePool(address(callback)).swapAndLiquify(); + } + + uint acceptAmount = amount - fee; + + _tOwned[from] = _tOwned[from].sub(amount); + _tOwned[to] = _tOwned[to].add(acceptAmount); + emit Transfer(from, to, acceptAmount); + } + + +} diff --git a/semgrep-rules/security/erc20-public-transfer.yaml b/semgrep-rules/security/erc20-public-transfer.yaml new file mode 100644 index 0000000..6b79ddc --- /dev/null +++ b/semgrep-rules/security/erc20-public-transfer.yaml @@ -0,0 +1,31 @@ +rules: + - + id: erc20-public-transfer + message: Custom ERC20 implementation exposes _transfer() as public + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: LOW + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://medium.com/@Knownsec_Blockchain_Lab/creat-future-was-tragically-transferred-coins-at-will-who-is-the-mastermind-behind-the-scenes-8ad42a7af814 + - https://bscscan.com/address/0x8B7218CF6Ac641382D7C723dE8aA173e98a80196 + patterns: + - pattern-either: + - pattern: | + function _transfer(...) public { ... } + - pattern: | + function _transfer(...) external { ... } + - pattern-not: | + function _transfer(...) $M { ... } + - pattern-not: | + function _transfer(...) $M(...) { ... } + languages: + - solidity + severity: WARNING + diff --git a/semgrep-rules/security/erc677-reentrancy.sol b/semgrep-rules/security/erc677-reentrancy.sol new file mode 100644 index 0000000..9ae1760 --- /dev/null +++ b/semgrep-rules/security/erc677-reentrancy.sol @@ -0,0 +1,1010 @@ + +// 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) public; +} + +// 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/upgradeable_contracts/Claimable.sol + +pragma solidity 0.4.24; + + + +contract Claimable { + bytes4 internal constant TRANSFER = 0xa9059cbb; // transfer(address,uint256) + + modifier validAddress(address _to) { + require(_to != address(0)); + /* solcov ignore next */ + _; + } + + function claimValues(address _token, address _to) internal { + if (_token == address(0)) { + claimNativeCoins(_to); + } else { + claimErc20Tokens(_token, _to); + } + } + + function claimNativeCoins(address _to) internal { + uint256 value = address(this).balance; + Address.safeSendValue(_to, value); + } + + function claimErc20Tokens(address _token, address _to) internal { + ERC20Basic token = ERC20Basic(_token); + uint256 balance = token.balanceOf(this); + safeTransfer(_token, _to, balance); + } + + function safeTransfer(address _token, address _to, uint256 _value) internal { + bytes memory returnData; + bool returnDataResult; + bytes memory callData = abi.encodeWithSelector(TRANSFER, _to, _value); + assembly { + let result := call(gas, _token, 0x0, add(callData, 0x20), mload(callData), 0, 32) + returnData := mload(0) + returnDataResult := mload(0) + + switch result + case 0 { + revert(0, 0) + } + } + + // Return data is optional + if (returnData.length > 0) { + require(returnDataResult); + } + } +} + +// 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; + + event ContractFallbackCallFailed(address from, address to, uint256 value); + + 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, 2, 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)); + // ruleid: erc677-reentrancy + 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; + } + + function callAfterTransfer(address _from, address _to, uint256 _value) internal { + if (AddressUtils.isContract(_to) && !contractFallback(_from, _to, _value, new bytes(0))) { + require(!isBridge(_to)); + emit ContractFallbackCallFailed(_from, _to, _value); + } + } + + 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(); + } + + function claimTokens(address _token, address _to) public onlyOwner validAddress(_to) { + 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 = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)"); + bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb; + + 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 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`. + /// @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(_holder != address(0)); + require(_spender != address(0)); + require(_expiry == 0 || _now() <= _expiry); + + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256(abi.encode(PERMIT_TYPEHASH, _holder, _spender, _nonce, _expiry, _allowed)) + ) + ); + + require(_holder == ecrecover(digest, _v, _r, _s)); + require(_nonce == nonces[_holder]++); + + uint256 amount = _allowed ? uint256(-1) : 0; + + allowed[_holder][_spender] = amount; + expirations[_holder][_spender] = _allowed ? _expiry : 0; + + emit Approval(_holder, _spender, amount); + } + + function _now() internal view returns (uint256) { + return now; + } + + /// @dev Version of the token contract. + function getTokenInterfacesVersion() external pure returns (uint64 major, uint64 minor, uint64 patch) { + return (2, 3, 0); + } +} + +// File: contracts/ERC677MultiBridgeToken.sol + +pragma solidity 0.4.24; + + +/** + * @title ERC677MultiBridgeToken + * @dev This contract extends ERC677BridgeToken to support several bridge simulteniously + */ +contract ERC677MultiBridgeToken is PermittableToken { + address public constant F_ADDR = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF; + uint256 internal constant MAX_BRIDGES = 50; + mapping(address => address) public bridgePointers; + uint256 public bridgeCount; + + event BridgeAdded(address indexed bridge); + event BridgeRemoved(address indexed bridge); + + constructor(string _name, string _symbol, uint8 _decimals, uint256 _chainId) + public + PermittableToken(_name, _symbol, _decimals, _chainId) + { + bridgePointers[F_ADDR] = F_ADDR; // empty bridge contracts list + } + + /** + * @dev Removes unused function from ERC677BridgeToken contract + */ + function setBridgeContract(address) external { + revert(); + } + + /** + * @dev Removes unused getter from ERC677BridgeToken contract + */ + function bridgeContract() external view returns (address) { + revert(); + } + + /** + * @dev Adds one more bridge contract into the list + * @param _bridge bridge contract address + */ + function addBridge(address _bridge) external onlyOwner { + require(bridgeCount < MAX_BRIDGES); + require(AddressUtils.isContract(_bridge)); + require(!isBridge(_bridge)); + + address firstBridge = bridgePointers[F_ADDR]; + require(firstBridge != address(0)); + bridgePointers[F_ADDR] = _bridge; + bridgePointers[_bridge] = firstBridge; + bridgeCount = bridgeCount.add(1); + + emit BridgeAdded(_bridge); + } + + /** + * @dev Removes one existing bridge contract from the list + * @param _bridge bridge contract address + */ + function removeBridge(address _bridge) external onlyOwner { + require(isBridge(_bridge)); + + address nextBridge = bridgePointers[_bridge]; + address index = F_ADDR; + address next = bridgePointers[index]; + require(next != address(0)); + + while (next != _bridge) { + index = next; + next = bridgePointers[index]; + + require(next != F_ADDR && next != address(0)); + } + + bridgePointers[index] = nextBridge; + delete bridgePointers[_bridge]; + bridgeCount = bridgeCount.sub(1); + + emit BridgeRemoved(_bridge); + } + + /** + * @dev Returns all recorded bridge contract addresses + * @return address[] bridge contract addresses + */ + function bridgeList() external view returns (address[]) { + address[] memory list = new address[](bridgeCount); + uint256 counter = 0; + address nextBridge = bridgePointers[F_ADDR]; + require(nextBridge != address(0)); + + while (nextBridge != F_ADDR) { + list[counter] = nextBridge; + nextBridge = bridgePointers[nextBridge]; + counter++; + + require(nextBridge != address(0)); + } + + return list; + } + + /** + * @dev Checks if given address is included into bridge contracts list + * @param _address bridge contract address + * @return bool true, if given address is a known bridge contract + */ + function isBridge(address _address) public view returns (bool) { + return _address != F_ADDR && bridgePointers[_address] != address(0); + } +} + +// File: contracts/ERC677MultiBridgeMintableToken.sol + +pragma solidity 0.4.24; + + +/** + * @title ERC677MultiBridgeMintableToken + * @dev This contract extends ERC677MultiBridgeToken to support several bridge simulteniously. + * Every bridge is allowed to mint tokens + */ +contract ERC677MultiBridgeMintableToken is ERC677MultiBridgeToken { + constructor(string _name, string _symbol, uint8 _decimals, uint256 _chainId) + public + ERC677MultiBridgeToken(_name, _symbol, _decimals, _chainId) + { + // solhint-disable-previous-line no-empty-blocks + } + modifier hasMintPermission() { + require(isBridge(msg.sender)); + _; + } +} diff --git a/semgrep-rules/security/erc677-reentrancy.yaml b/semgrep-rules/security/erc677-reentrancy.yaml new file mode 100644 index 0000000..02826be --- /dev/null +++ b/semgrep-rules/security/erc677-reentrancy.yaml @@ -0,0 +1,29 @@ +rules: + - + id: erc677-reentrancy + message: ERC677 callAfterTransfer() reentrancy + metadata: + category: security + technology: + - solidity + cwe: "CWE-841: Improper Enforcement of Behavioral Workflow" + confidence: HIGH + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/peckshield/status/1509431646818234369 + - https://twitter.com/blocksecteam/status/1509466576848064512 + - https://explorer.fuse.io/address/0x139Eb08579eec664d461f0B754c1F8B569044611 # Ola + - https://explorer.fuse.io/address/0x5De15b5543c178C111915d6B8ae929Af01a8cC58 # WETH + patterns: + - pattern-inside: | + function transfer(...) { + ... + } + - pattern: callAfterTransfer(...); + languages: + - solidity + severity: WARNING + diff --git a/semgrep-rules/security/erc721-arbitrary-transferfrom.sol b/semgrep-rules/security/erc721-arbitrary-transferfrom.sol new file mode 100644 index 0000000..e6fe84e --- /dev/null +++ b/semgrep-rules/security/erc721-arbitrary-transferfrom.sol @@ -0,0 +1,2035 @@ +/** + *Submitted for verification at Etherscan.io on 2022-02-07 +*/ + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/* +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@&@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@&@@&@@@@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@&&@@@@@@@ +@@@@@@@@@@@@@@&@@&@@&@&&&@&@&@@&&@@@&@&&&%@@@@@&@@@@&&&@@@@&&@&&@@&@@@&%&&&@@@@@@@@@@@@@ +@@@@@@@@@@@&@&%@@&@@@@@@@@@@@@&&@@@@@@@@@@@@@@@@@@@@@@&@@@@@@@@@&@@@@@@@@@&@@@@@@@@@@@@@ +@@@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&@@@@@@@@@&@@@@@@ +@@@@@@@@@@@@@&@@@&@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&&@@@@@@@@@ +@@@@@@@@@@@@@&&@&&@@@@@&&@@@@&%@@&@&@@&@&&@@&&&@@&@%@&@%@@&@@%@&&@@@&@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@&@@%@@&@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&@@@&@@&@@&@@@@@@@@@@@@@ +@@@@@@@@@@&@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&&@@@@@@@@@@@@@@@@@@@&@&&@@&@@@@@@@@@@@@@@@@ +@@@@@@@@@@@&@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@&&@@@@@@@@@@@@@@@@@@@&@@@@@@@@@&@@@@@@@@@@@@@ +@@@@@@&@@@@&@@&@@@@@@@@&@@@@@@&@@@@@@@@@@&&&&@@&@@@@@@@@@@@@@@@@%@@@@@@@@@&@@@@@@&@@@@@@ +@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&&&&&&@@@@@@@@@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@&@@@@@@&@@@@@@@@@@@@@&&&&&&&@&@&@@@@@@@@@@@@@@@@@@@@@@@@@&@@@@@@&@@@@@@ +@@@@@@@@@@@@@@&@@@@@@@@@%@@@@@@@@@@@&&&&&@@@@@@@@@@@@@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@&@@&@@@@@@@@@%@@@@&@@@@@&&@@@@@@@@@@@@@@@@@@@@@@@@@@@%@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@&@@@@@@@@@&@@@@@@@@@@@@&@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@&@@@@@&@@@%@@@@@@ +@@@@@@@&@@@&@@%@@&@@@@@@&@@@@@@@@@@@@&&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&@&@@&@@@&@&@@@@@@@ +@@@@@@@@@@@@@@&@@&@@@@@@&@@@@@@@@@@@@@&&&&@&@@@@@@@@@@@@@@@@@@@@@@@@%@@@@@@@@@&@@@@@@@@@ +@@@@@@&@@@@@&@@@@@&@@@@&@@@@@@@@@@@@@@@@&&&&&@&@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@&@@@@@@@ +@@@@@@@&@@@@@@&@&@@&@@@&&@@@@@@@@@@@@@@@@@&&&@@@@@@@@@@@@@@@@@@@@@@@@@&@@@&@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@&&@@@@@@@@@@@@@@@@@@@&@@@@@@@@@@&@@@@@@@@@@@@@@@&@@&@@@@&@@@@@@@@ +@@@@@@@@@@@@@@@@@&@@@@@&%@@@@&@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&@@@@@@@@@&@@@@@@ +@@@@@@@@@@@@@@@@@@@@&@@&@&@@@&@@@%&@@%@@@&&@@@@@@&@@&&@@@&%&&&@&&@@@@@@@@@&@@@&@&@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&@@@@@&@@@@@@@@@@@&@@@@@@ +@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&@@&@@@@@&@@@@@@@ +@@@@@@@@@@@&@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@&@@@&@@@&&&&@&&@@&&&&@@@@%@&&&&%&&@%@@@&@&@@@&%@@%&&&&@&&&&&@@&&@@%&@@@@@@@@@@@@@ +@@@@@@@@@@@@@@&@@@@@@&@@@@@&@@@@@@@@@@@@&@@@@@&@@@&@@@@@@@@@@@@@@@@@&@@&@&@@@@@@&@@@@@@@ +@@@@@@@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM + +Gas optimization credited to Azuki: https://www.erc721a.org/ +*/ + + + + +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); +} + +pragma solidity ^0.8.0; + + +/** + * @dev Implementation of the {IERC165} interface. + * + * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check + * for the additional interface id that will be supported. For example: + * + * ```solidity + * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); + * } + * ``` + * + * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. + */ +abstract contract ERC165 is IERC165 { + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IERC165).interfaceId; + } +} + +pragma solidity ^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; + 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); + } + } + } +} + + + +pragma solidity ^0.8.0; + + +/** + * @dev Required interface of an ERC721 compliant contract. + */ +interface IERC721 is IERC165 { + /** + * @dev Emitted when `tokenId` token is transferred from `from` to `to`. + */ + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. + */ + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + */ + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /** + * @dev Returns the number of tokens in ``owner``'s account. + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @dev Returns the owner of the `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function ownerOf(uint256 tokenId) external view returns (address owner); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients + * are aware of the ERC721 protocol to prevent tokens from being forever locked. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Transfers `tokenId` token from `from` to `to`. + * + * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Gives permission to `to` to transfer `tokenId` token to another account. + * The approval is cleared when the token is transferred. + * + * Only a single account can be approved at a time, so approving the zero address clears previous approvals. + * + * Requirements: + * + * - The caller must own the token or be an approved operator. + * - `tokenId` must exist. + * + * Emits an {Approval} event. + */ + function approve(address to, uint256 tokenId) external; + + /** + * @dev Returns the account approved for `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function getApproved(uint256 tokenId) external view returns (address operator); + + /** + * @dev Approve or remove `operator` as an operator for the caller. + * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. + * + * Requirements: + * + * - The `operator` cannot be the caller. + * + * Emits an {ApprovalForAll} event. + */ + function setApprovalForAll(address operator, bool _approved) external; + + /** + * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. + * + * See {setApprovalForAll} + */ + function isApprovedForAll(address owner, address operator) external view returns (bool); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external; +} + + + +pragma solidity ^0.8.0; + +/** + * @title ERC721 token receiver interface + * @dev Interface for any contract that wants to support safeTransfers + * from ERC721 asset contracts. + */ +interface IERC721Receiver { + /** + * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} + * by `operator` from `from`, this function is called. + * + * It must return its Solidity selector to confirm the token transfer. + * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. + * + * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`. + */ + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) external returns (bytes4); +} + + + +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; + } +} + + +pragma solidity ^0.8.0; + +/** + * @dev String operations. + */ +library Strings { + bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; + + /** + * @dev Converts a `uint256` to its ASCII `string` decimal representation. + */ + function toString(uint256 value) internal pure returns (string memory) { + // Inspired by OraclizeAPI's implementation - MIT licence + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. + */ + function toHexString(uint256 value) internal pure returns (string memory) { + if (value == 0) { + return "0x00"; + } + uint256 temp = value; + uint256 length = 0; + while (temp != 0) { + length++; + temp >>= 8; + } + return toHexString(value, length); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + */ + function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = _HEX_SYMBOLS[value & 0xf]; + value >>= 4; + } + require(value == 0, "Strings: hex length insufficient"); + return string(buffer); + } +} + + + +pragma solidity ^0.8.0; + + +/** + * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Enumerable is IERC721 { + /** + * @dev Returns the total amount of tokens stored by the contract. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns a token ID owned by `owner` at a given `index` of its token list. + * Use along with {balanceOf} to enumerate all of ``owner``'s tokens. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId); + + /** + * @dev Returns a token ID at a given `index` of all the tokens stored by the contract. + * Use along with {totalSupply} to enumerate all tokens. + */ + function tokenByIndex(uint256 index) external view returns (uint256); +} + + + +pragma solidity ^0.8.0; + + + +/** + * @title ERC-721 Non-Fungible Token Standard, optional metadata extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Metadata is IERC721 { + /** + * @dev Returns the token collection name. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the token collection symbol. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token. + */ + function tokenURI(uint256 tokenId) external view returns (string memory); +} + + + +pragma solidity ^0.8.0; + +/** + * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including + * the Metadata and Enumerable extension. Built to optimize for lower gas during batch mints. + * + * Assumes serials are sequentially minted starting at 0 (e.g. 0, 1, 2, 3..). + * + * Assumes the number of issuable tokens (collection size) is capped and fits in a uint128. + * + * Does not support burning tokens to address(0). + */ +contract ERC721A is + Context, + ERC165, + IERC721, + IERC721Metadata, + IERC721Enumerable +{ + using Address for address; + using Strings for uint256; + + struct TokenOwnership { + address addr; + uint64 startTimestamp; + } + + struct AddressData { + uint128 balance; + uint128 numberMinted; + } + + uint256 private currentIndex = 1; + + uint256 internal immutable collectionSize; + uint256 internal immutable maxBatchSize; + + // Token name + string private _name; + + // Token symbol + string private _symbol; + + // Mapping from token ID to ownership details + // An empty struct value does not necessarily mean the token is unowned. See ownershipOf implementation for details. + mapping(uint256 => TokenOwnership) private _ownerships; + + // Mapping owner address to address data + mapping(address => AddressData) private _addressData; + + // Mapping from token ID to approved address + mapping(uint256 => address) private _tokenApprovals; + + // Mapping from owner to operator approvals + mapping(address => mapping(address => bool)) private _operatorApprovals; + + /** + * @dev + * `maxBatchSize` refers to how much a minter can mint at a time. + * `collectionSize_` refers to how many tokens are in the collection. + */ + constructor( + string memory name_, + string memory symbol_, + uint256 maxBatchSize_, + uint256 collectionSize_ + ) { + require( + collectionSize_ > 0, + "ERC721A: collection must have a nonzero supply" + ); + require(maxBatchSize_ > 0, "ERC721A: max batch size must be nonzero"); + _name = name_; + _symbol = symbol_; + maxBatchSize = maxBatchSize_; + collectionSize = collectionSize_; + } + + /** + * @dev See {IERC721Enumerable-totalSupply}. + */ + function totalSupplyA() private view returns (uint256) { + return currentIndex; + } + + function totalSupply() public view override returns (uint256) { + return currentIndex - 1; + } + + /** + * @dev See {IERC721Enumerable-tokenByIndex}. + */ + function tokenByIndex(uint256 index) public view override returns (uint256) { + require(index < totalSupplyA(), "ERC721A: global index out of bounds"); + return index; + } + + /** + * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}. + * This read function is O(collectionSize). If calling from a separate contract, be sure to test gas first. + * It may also degrade with extremely large collection sizes (e.g >> 10000), test for your use case. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) + public + view + override + returns (uint256) + { + require(index < balanceOf(owner), "ERC721A: owner index out of bounds"); + uint256 numMintedSoFar = totalSupplyA(); + uint256 tokenIdsIdx = 0; + address currOwnershipAddr = address(0); + for (uint256 i = 0; i < numMintedSoFar; i++) { + TokenOwnership memory ownership = _ownerships[i]; + if (ownership.addr != address(0)) { + currOwnershipAddr = ownership.addr; + } + if (currOwnershipAddr == owner) { + if (tokenIdsIdx == index) { + return i; + } + tokenIdsIdx++; + } + } + revert("ERC721A: unable to get token of owner by index"); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(ERC165, IERC165) + returns (bool) + { + return + interfaceId == type(IERC721).interfaceId || + interfaceId == type(IERC721Metadata).interfaceId || + interfaceId == type(IERC721Enumerable).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC721-balanceOf}. + */ + function balanceOf(address owner) public view override returns (uint256) { + require(owner != address(0), "ERC721A: balance query for the zero address"); + return uint256(_addressData[owner].balance); + } + + function _numberMinted(address owner) internal view returns (uint256) { + require( + owner != address(0), + "ERC721A: number minted query for the zero address" + ); + return uint256(_addressData[owner].numberMinted); + } + + function ownershipOf(uint256 tokenId) + internal + view + returns (TokenOwnership memory) + { + require(_exists(tokenId), "ERC721A: owner query for nonexistent token"); + + uint256 lowestTokenToCheck; + if (tokenId >= maxBatchSize) { + lowestTokenToCheck = tokenId - maxBatchSize + 1; + } + + for (uint256 curr = tokenId; curr >= lowestTokenToCheck; curr--) { + TokenOwnership memory ownership = _ownerships[curr]; + if (ownership.addr != address(0)) { + return ownership; + } + } + + revert("ERC721A: unable to determine the owner of token"); + } + + /** + * @dev See {IERC721-ownerOf}. + */ + function ownerOf(uint256 tokenId) public view override returns (address) { + return ownershipOf(tokenId).addr; + } + + /** + * @dev See {IERC721Metadata-name}. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev See {IERC721Metadata-symbol}. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev See {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) + public + view + virtual + override + returns (string memory) + { + require( + _exists(tokenId), + "ERC721Metadata: URI query for nonexistent token" + ); + + string memory baseURI = _baseURI(); + return + bytes(baseURI).length > 0 + ? string(abi.encodePacked(baseURI, tokenId.toString())) + : ""; + } + + /** + * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each + * token will be the concatenation of the `baseURI` and the `tokenId`. Empty + * by default, can be overriden in child contracts. + */ + function _baseURI() internal view virtual returns (string memory) { + return ""; + } + + /** + * @dev See {IERC721-approve}. + */ + function approve(address to, uint256 tokenId) public override { + address owner = ERC721A.ownerOf(tokenId); + require(to != owner, "ERC721A: approval to current owner"); + + require( + _msgSender() == owner || isApprovedForAll(owner, _msgSender()), + "ERC721A: approve caller is not owner nor approved for all" + ); + + _approve(to, tokenId, owner); + } + + /** + * @dev See {IERC721-getApproved}. + */ + function getApproved(uint256 tokenId) public view override returns (address) { + require(_exists(tokenId), "ERC721A: approved query for nonexistent token"); + + return _tokenApprovals[tokenId]; + } + + /** + * @dev See {IERC721-setApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) public override { + require(operator != _msgSender(), "ERC721A: approve to caller"); + + _operatorApprovals[_msgSender()][operator] = approved; + emit ApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev See {IERC721-isApprovedForAll}. + */ + function isApprovedForAll(address owner, address operator) + public + view + virtual + override + returns (bool) + { + return _operatorApprovals[owner][operator]; + } + + /** + * @dev See {IERC721-transferFrom}. + */ + + function transferFrom( + address from, + address to, + uint256 tokenId + ) public virtual override { + _transfer(from, to, tokenId); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) public override { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) public virtual override { + _transfer(from, to, tokenId); + require( + _checkOnERC721Received(from, to, tokenId, _data), + "ERC721A: transfer to non ERC721Receiver implementer" + ); + } + + /** + * @dev Returns whether `tokenId` exists. + * + * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. + * + * Tokens start existing when they are minted (`_mint`), + */ + function _exists(uint256 tokenId) internal view returns (bool) { + return tokenId < currentIndex; + } + + function _safeMint(address to, uint256 quantity) internal { + _safeMint(to, quantity, ""); + } + + function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { + require(_exists(tokenId), "ERC721: operator query for nonexistent token"); + address owner = ERC721A.ownerOf(tokenId); + return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); + } + /** + * @dev Mints `quantity` tokens and transfers them to `to`. + * + * Requirements: + * + * - there must be `quantity` tokens remaining unminted in the total collection. + * - `to` cannot be the zero address. + * - `quantity` cannot be larger than the max batch size. + * + * Emits a {Transfer} event. + */ + + + + function _safeMint( + address to, + uint256 quantity, + bytes memory _data + ) internal { + uint256 startTokenId = currentIndex; + require(to != address(0), "ERC721A: mint to the zero address"); + // We know if the first token in the batch doesn't exist, the other ones don't as well, because of serial ordering. + require(!_exists(startTokenId), "ERC721A: token already minted"); + require(quantity <= maxBatchSize, "ERC721A: quantity to mint too high"); + + _beforeTokenTransfers(address(0), to, startTokenId, quantity); + + AddressData memory addressData = _addressData[to]; + _addressData[to] = AddressData( + addressData.balance + uint128(quantity), + addressData.numberMinted + uint128(quantity) + ); + _ownerships[startTokenId] = TokenOwnership(to, uint64(block.timestamp)); + + uint256 updatedIndex = startTokenId; + + for (uint256 i = 0; i < quantity; i++) { + emit Transfer(address(0), to, updatedIndex); + require( + _checkOnERC721Received(address(0), to, updatedIndex, _data), + "ERC721A: transfer to non ERC721Receiver implementer" + ); + updatedIndex++; + } + + currentIndex = updatedIndex; + _afterTokenTransfers(address(0), to, startTokenId, quantity); + } + + /** + * @dev Transfers `tokenId` from `from` to `to`. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * + * Emits a {Transfer} event. + */ + function _transfer( + address from, + address to, + uint256 tokenId + ) internal virtual { + TokenOwnership memory prevOwnership = ownershipOf(tokenId); + + require( + prevOwnership.addr == from, + "ERC721A: transfer from incorrect owner" + ); + require(to != address(0), "ERC721A: transfer to the zero address"); + + _beforeTokenTransfers(from, to, tokenId, 1); + + // Clear approvals from the previous owner + // ruleid: erc721-arbitrary-transferfrom + _approve(address(0), tokenId, prevOwnership.addr); + + _addressData[from].balance -= 1; + _addressData[to].balance += 1; + _ownerships[tokenId] = TokenOwnership(to, uint64(block.timestamp)); + + // If the ownership slot of tokenId+1 is not explicitly set, that means the transfer initiator owns it. + // Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls. + uint256 nextTokenId = tokenId + 1; + if (_ownerships[nextTokenId].addr == address(0)) { + if (_exists(nextTokenId)) { + _ownerships[nextTokenId] = TokenOwnership( + prevOwnership.addr, + prevOwnership.startTimestamp + ); + } + } + + emit Transfer(from, to, tokenId); + _afterTokenTransfers(from, to, tokenId, 1); + } + + // correct _transfer from Azuki ERC721A https://github.com/chiru-labs/ERC721A/blob/main/contracts/ERC721A.sol#L442= + function _transfer( + address from, + address to, + uint256 tokenId + ) private { + TokenOwnership memory prevOwnership = _ownershipOf(tokenId); + + if (prevOwnership.addr != from) revert TransferFromIncorrectOwner(); + + bool isApprovedOrOwner = (_msgSender() == from || + isApprovedForAll(from, _msgSender()) || + getApproved(tokenId) == _msgSender()); + + if (!isApprovedOrOwner) revert TransferCallerNotOwnerNorApproved(); + if (to == address(0)) revert TransferToZeroAddress(); + + _beforeTokenTransfers(from, to, tokenId, 1); + + // Clear approvals from the previous owner + // ok: erc721-arbitrary-transferfrom + _approve(address(0), tokenId, from); + + // Underflow of the sender's balance is impossible because we check for + // ownership above and the recipient's balance can't realistically overflow. + // Counter overflow is incredibly unrealistic as tokenId would have to be 2**256. + unchecked { + _addressData[from].balance -= 1; + _addressData[to].balance += 1; + + TokenOwnership storage currSlot = _ownerships[tokenId]; + currSlot.addr = to; + currSlot.startTimestamp = uint64(block.timestamp); + + // If the ownership slot of tokenId+1 is not explicitly set, that means the transfer initiator owns it. + // Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls. + uint256 nextTokenId = tokenId + 1; + TokenOwnership storage nextSlot = _ownerships[nextTokenId]; + if (nextSlot.addr == address(0)) { + // This will suffice for checking _exists(nextTokenId), + // as a burned slot cannot contain the zero address. + if (nextTokenId != _currentIndex) { + nextSlot.addr = from; + nextSlot.startTimestamp = prevOwnership.startTimestamp; + } + } + } + + emit Transfer(from, to, tokenId); + _afterTokenTransfers(from, to, tokenId, 1); + } + + // correct _transfer from OpenZeppelin ERC721 + function _transfer( + address from, + address to, + uint256 tokenId + ) internal virtual { + require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); + require(to != address(0), "ERC721: transfer to the zero address"); + + _beforeTokenTransfer(from, to, tokenId); + + // Clear approvals from the previous owner + // ok: erc721-arbitrary-transferfrom + _approve(address(0), tokenId); + + _balances[from] -= 1; + _balances[to] += 1; + _owners[tokenId] = to; + + emit Transfer(from, to, tokenId); + + _afterTokenTransfer(from, to, tokenId); + } + + // correct transferFrom from OpenZeppelin + + function transferFrom( + address from, + address to, + uint256 tokenId + ) public virtual override { + //solhint-disable-next-line max-line-length + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); + // ok: erc721-arbitrary-transferfrom + _transfer(from, to, tokenId); + } + + // another correct transfer + function _transfer( + address from, + address to, + uint256 tokenId + ) private { + TokenOwnership memory prevOwnership = ownershipOf(tokenId); + + bool isApprovedOrOwner = (_msgSender() == prevOwnership.addr || + getApproved(tokenId) == _msgSender() || + isApprovedForAll(prevOwnership.addr, _msgSender())); + + require( + isApprovedOrOwner, + "ERC721A: transfer caller is not owner nor approved" + ); + + require( + prevOwnership.addr == from, + "ERC721A: transfer from incorrect owner" + ); + require(to != address(0), "ERC721A: transfer to the zero address"); + + _beforeTokenTransfers(from, to, tokenId, 1); + + // Clear approvals from the previous owner + // ok: erc721-arbitrary-transferfrom + _approve(address(0), tokenId, prevOwnership.addr); + + _addressData[from].balance -= 1; + _addressData[to].balance += 1; + _ownerships[tokenId] = TokenOwnership(to, uint64(block.timestamp)); + + // If the ownership slot of tokenId+1 is not explicitly set, that means the transfer initiator owns it. + // Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls. + uint256 nextTokenId = tokenId + 1; + if (_ownerships[nextTokenId].addr == address(0)) { + if (_exists(nextTokenId)) { + _ownerships[nextTokenId] = TokenOwnership( + prevOwnership.addr, + prevOwnership.startTimestamp + ); + } + } + + emit Transfer(from, to, tokenId); + _afterTokenTransfers(from, to, tokenId, 1); + } + + // ERC721X + function _transfer( + address from, + address to, + uint256 tokenId + ) internal virtual { + TokenOwnership memory prevOwnership = ownershipOf(tokenId); + + //bool isApprovedOrOwner = (_msgSender() == prevOwnership.addr || + //getApproved(tokenId) == _msgSender() || + //isApprovedForAll(prevOwnership.addr, _msgSender())); + + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721X: transfer caller is not owner nor approved"); + require(prevOwnership.addr == from, "ERC721X: transfer from incorrect owner"); + require(to != address(0), "ERC721X: transfer to the zero address"); + + _beforeTokenTransfers(from, to, tokenId, 1); + + // Clear approvals from the previous owner + // ok: erc721-arbitrary-transferfrom + _approve(address(0), tokenId, prevOwnership.addr); + + _addressData[from].balance -= 1; + _addressData[to].balance += 1; + _ownerships[tokenId] = TokenOwnership(to, uint64(block.timestamp)); + + // If the ownership slot of tokenId+1 is not explicitly set, that means the transfer initiator owns it. + // Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls. + uint256 nextTokenId = tokenId + 1; + if (_ownerships[nextTokenId].addr == address(0)) { + if (_exists(nextTokenId)) { + _ownerships[nextTokenId] = TokenOwnership( + prevOwnership.addr, + prevOwnership.startTimestamp + ); + } + } + + emit Transfer(from, to, tokenId); + _afterTokenTransfers(from, to, tokenId, 1); + } + + /** + * @dev Approve `to` to operate on `tokenId` + * + * Emits a {Approval} event. + */ + function _approve( + address to, + uint256 tokenId, + address owner + ) private { + _tokenApprovals[tokenId] = to; + emit Approval(owner, to, tokenId); + } + + uint256 public nextOwnerToExplicitlySet = 0; + + /** + * @dev Explicitly set `owners` to eliminate loops in future calls of ownerOf(). + */ + function _setOwnersExplicit(uint256 quantity) internal { + uint256 oldNextOwnerToSet = nextOwnerToExplicitlySet; + require(quantity > 0, "quantity must be nonzero"); + uint256 endIndex = oldNextOwnerToSet + quantity - 1; + if (endIndex > collectionSize - 1) { + endIndex = collectionSize - 1; + } + // We know if the last one in the group exists, all in the group exist, due to serial ordering. + require(_exists(endIndex), "not enough minted yet for this cleanup"); + for (uint256 i = oldNextOwnerToSet; i <= endIndex; i++) { + if (_ownerships[i].addr == address(0)) { + TokenOwnership memory ownership = ownershipOf(i); + _ownerships[i] = TokenOwnership( + ownership.addr, + ownership.startTimestamp + ); + } + } + nextOwnerToExplicitlySet = endIndex + 1; + } + + /** + * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address. + * The call is not executed if the target address is not a contract. + * + * @param from address representing the previous owner of the given token ID + * @param to target address that will receive the tokens + * @param tokenId uint256 ID of the token to be transferred + * @param _data bytes optional data to send along with the call + * @return bool whether the call correctly returned the expected magic value + */ + function _checkOnERC721Received( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) private returns (bool) { + if (to.isContract()) { + try + IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) + returns (bytes4 retval) { + return retval == IERC721Receiver(to).onERC721Received.selector; + } catch (bytes memory reason) { + if (reason.length == 0) { + revert("ERC721A: transfer to non ERC721Receiver implementer"); + } else { + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } else { + return true; + } + } + + /** + * @dev Hook that is called before a set of serially-ordered token ids are about to be transferred. This includes minting. + * + * startTokenId - the first token id to be transferred + * quantity - the amount to be transferred + * + * Calling conditions: + * + * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be + * transferred to `to`. + * - When `from` is zero, `tokenId` will be minted for `to`. + */ + function _beforeTokenTransfers( + address from, + address to, + uint256 startTokenId, + uint256 quantity + ) internal virtual {} + + /** + * @dev Hook that is called after a set of serially-ordered token ids have been transferred. This includes + * minting. + * + * startTokenId - the first token id to be transferred + * quantity - the amount to be transferred + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero. + * - `from` and `to` are never both zero. + */ + function _afterTokenTransfers( + address from, + address to, + uint256 startTokenId, + uint256 quantity + ) internal virtual {} +} + + + +pragma solidity ^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() { + _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; + } +} + + + +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); + } +} + + +pragma solidity ^0.8.0; + + +contract Distortion is ERC721A, Ownable, ReentrancyGuard { + + + + bool public isClaimActive = false; + uint public maxSupply = 1000; + uint public maxFree = 200; + uint256 public price = 0.05 ether; + address payable private immutable payoutAddress; + + + // keep track of wallets that have claimed + mapping(address => uint) private claimed; + mapping(address => uint) private claimedFree; + + // randomness of block in the future (#xxxxxxxx) + string private hashOfBlock; + bool private hashSet = false; + + constructor( + address payable _payoutAddress + ) ERC721A("Distortion", "DST", 1, maxSupply) { + + require(_payoutAddress != address(0)); + payoutAddress = _payoutAddress; + + } + + + /* + + ███╗░░░███╗██╗███╗░░██╗████████╗ + ████╗░████║██║████╗░██║╚══██╔══╝ + ██╔████╔██║██║██╔██╗██║░░░██║░░░ + ██║╚██╔╝██║██║██║╚████║░░░██║░░░ + ██║░╚═╝░██║██║██║░╚███║░░░██║░░░ + ╚═╝░░░░░╚═╝╚═╝╚═╝░░╚══╝░░░╚═╝░░░ + Information on 0xDistortion.com + */ + + function mint() public payable callerIsWallet { + require(isClaimActive, "Claim is not active..."); + require(totalSupply() + 1 <= maxSupply, "We have no more supply in stock."); + + if (totalSupply() + 1 > maxFree) { + require(price == msg.value, "Wrong ether amount. Free supply depleted."); + require(claimed[msg.sender] + 1 <= 2, "Max 2 per wallet."); + claimed[msg.sender] += 1; + + } else { + require(msg.value == 0, "Don't send ether for the free mint."); + require(claimedFree[msg.sender] < 1, "You can only get 1 free mint."); + claimedFree[msg.sender] += 1; + } + + _safeMint(msg.sender, 1); + } + + + + /* + ██████╗░███████╗██╗░░░██╗ + ██╔══██╗██╔════╝██║░░░██║ + ██║░░██║█████╗░░╚██╗░██╔╝ + ██║░░██║██╔══╝░░░╚████╔╝░ + ██████╔╝███████╗░░╚██╔╝░░ + ╚═════╝░╚══════╝░░░╚═╝░░░ + Developer functions: + - Dev mint + - Dev airdrop + - Start the sale/claim + - Update the price of the sale + - Update the hash/provenance for the reveal (can only be called once) + */ + + function devMint() public onlyOwner { + require(totalSupply() + 1 <= maxSupply, "Creator cannot mint if supply has been reached."); + _safeMint(msg.sender, 1); + } + + function devAirdrop(address _recipient) public onlyOwner { + require(totalSupply() + 1 <= maxSupply, "Creator cannot airdrop if supply has been reached."); + _safeMint(_recipient, 1); + } + + + function setClaimState(bool _trueOrFalse) public onlyOwner { + isClaimActive = _trueOrFalse; + } + + // adding the hash of the block of the last mint to ensure randomness. + function updateHash(string memory _hash) public onlyOwner { + require(!hashSet, "Randomness hash can only be set once by the deployer."); + hashOfBlock = _hash; + hashSet = true; + } + + function updatePrice(uint256 _price) public onlyOwner { + price = _price; + } + + + + /* + + ░██████╗░███████╗███╗░░██╗███████╗██████╗░░█████╗░████████╗██╗██╗░░░██╗███████╗ + ██╔════╝░██╔════╝████╗░██║██╔════╝██╔══██╗██╔══██╗╚══██╔══╝██║██║░░░██║██╔════╝ + ██║░░██╗░█████╗░░██╔██╗██║█████╗░░██████╔╝███████║░░░██║░░░██║╚██╗░██╔╝█████╗░░ + ██║░░╚██╗██╔══╝░░██║╚████║██╔══╝░░██╔══██╗██╔══██║░░░██║░░░██║░╚████╔╝░██╔══╝░░ + ╚██████╔╝███████╗██║░╚███║███████╗██║░░██║██║░░██║░░░██║░░░██║░░╚██╔╝░░███████╗ + ░╚═════╝░╚══════╝╚═╝░░╚══╝╚══════╝╚═╝░░╚═╝╚═╝░░╚═╝░░░╚═╝░░░╚═╝░░░╚═╝░░░╚══════╝ + Everything to do with the on-chain generation of the Distortion pieces. + */ + + + + string[] private colorNames = [ + 'White' , + 'Red', + 'Green', + 'Blue', + 'Gold', + 'Rose', + 'Purple' + ]; + + + string[] private left = [ + 'rgb(140,140,140)' , + 'rgb(255, 26, 26)', + 'rgb(92, 214, 92)', + 'rgb(26, 140, 255)', + 'rgb(255, 215, 0)', + 'rgb(255, 128, 128)', + 'rgb(192, 50, 227)' + ]; + + string[] private right = [ + 'rgb(52,52,52)' , + 'rgb(230, 0, 0)', + 'rgb(51, 204, 51)', + 'rgb(0, 115, 230)', + 'rgb(204, 173, 0)', + 'rgb(255, 102, 102)', + 'rgb(167, 40, 199)' + ]; + + string[] private middleLeft = [ + 'rgb(57,57,57)' , + 'rgb(179, 0, 0)', + 'rgb(41, 163, 41)', + 'rgb(0, 89, 179)', + 'rgb(153, 130, 0)', + 'rgb(255, 77, 77)', + 'rgb(127, 32, 150)' + ]; + + string[] private middleRight = [ + 'rgb(20,20,20)', + 'rgb(128, 0, 0)', + 'rgb(31, 122, 31)', + 'rgb(0, 64, 128)', + 'rgb(179, 152, 0)', + 'rgb(255, 51, 51)', + 'rgb(98, 19, 117)' + ]; + + + + + string[] private frequencies = [ + '', + '0', + '00' + ]; + + + function generateString(string memory name, uint256 tokenId, string[] memory array) internal view returns (string memory) { + + uint rand = uint(keccak256(abi.encodePacked(name, toString(tokenId)))) % array.length; + string memory output = string(abi.encodePacked(array[rand % array.length])); + return output; + + } + + + function generateColorNumber(string memory name, uint256 tokenId) internal view returns (uint) { + + uint output; + uint rand = uint(keccak256(abi.encodePacked(name, toString(tokenId)))) % 100; + + if (keccak256(bytes(hashOfBlock)) == keccak256(bytes(''))) { + output = 0; //unrevealed + } else { + if (rand <= 15) { + output = 1; //Red with 15% rarity. + } else if (rand > 15 && rand <= 30) { + output = 2; //Green with 15% rarity. + } else if (rand > 30 && rand <= 45) { + output = 3; //Blue with 15% rarity. + } else if (rand > 45 && rand <= 75) { + output = 0; //Black with 30% rarity. + } else if (rand > 75 && rand <= 80) { + output = 4; //Gold with 5% rarity. + } else if (rand > 80 && rand <= 90) { + output = 5; //Rose with 10% rarity. + } else if (rand > 90) { + output = 6; //Purple with 10% rarity. + } + + } + return output; + } + + + function generateNum(string memory name, uint256 tokenId, string memory genVar, uint low, uint high) internal view returns (string memory) { + + uint difference = high - low; + uint randomnumber = uint(keccak256(abi.encodePacked(genVar, tokenId, name))) % difference + 1; + randomnumber = randomnumber + low; + return toString(randomnumber); + + } + + function generateNumUint(string memory name, uint256 tokenId, string memory genVar, uint low, uint high) internal view returns (uint) { + + uint difference = high - low; + uint randomnumber = uint(keccak256(abi.encodePacked(genVar, tokenId, name))) % difference + 1; + randomnumber = randomnumber + low; + return randomnumber; + + } + + + function genDefs(uint256 tokenId) internal view returns (string memory) { + + string memory output; + string memory xFrequency = generateString("xF", tokenId, frequencies); + string memory yFrequency = generateString("yF", tokenId, frequencies); + string memory scale = generateNum("scale", tokenId, hashOfBlock, 10, 40); + + if (keccak256(bytes(hashOfBlock)) == keccak256(bytes(''))) { + xFrequency = ''; + yFrequency = ''; + scale = '30'; + } + + output = string(abi.encodePacked( + ' ' + )); + return output; + + } + + + function genMiddle(uint256 tokenId) internal view returns (string memory) { + + string memory translate = toString(divide(generateNumUint("scale", tokenId, hashOfBlock, 10, 40), 5, 0)); + string[5] memory p; + + if (keccak256(bytes(hashOfBlock)) == keccak256(bytes(''))) { + translate = '6'; + } + + p[0] = ' '; + + string memory output = string(abi.encodePacked(p[0], p[1], p[2], p[3], p[4])); + return output; + + } + + + + function genSquares(uint256 tokenId) internal view returns (string memory) { + + string memory output1; + string memory output2; + uint ringCount = generateNumUint("ringCount", tokenId, hashOfBlock, 5, 15); + string[2] memory xywh; + uint ringScaling = divide(25, ringCount, 0); + + if (keccak256(bytes(hashOfBlock)) == keccak256(bytes(''))) { + ringCount = 5; + ringScaling = 5; + } + + for (uint i = 0; i < ringCount; i++) { + xywh[0] = toString(ringScaling*i + 5); + xywh[1] = toString(100 - (ringScaling*i + 5)*2); + output1 = string(abi.encodePacked( + ' ' + )); + output2 = string(abi.encodePacked(output1, output2)); + } + return output2; + + } + + + function genEnd(uint256 tokenId) internal view returns (string memory) { + + uint colorNum = generateColorNumber("color", tokenId); + string[13] memory p; + p[0] = ' '; + string memory output = string(abi.encodePacked(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8])); + output = string(abi.encodePacked(output, p[9], p[10], p[11], p[12])); + return output; + + } + + + function generateDistortion(uint256 tokenId) public view returns (string memory) { + + string memory output; + output = string(abi.encodePacked( + ' ', + genDefs(tokenId), + genMiddle(tokenId), + genSquares(tokenId), + genEnd(tokenId) + )); + return output; + + } + + + function getFrequency(uint256 tokenId) internal view returns (uint) { + + uint[2] memory xy; + string memory y = generateString("yF", tokenId, frequencies); + string memory x = generateString("xF", tokenId, frequencies); + + if (keccak256(bytes(x)) == keccak256(bytes('0'))){ + xy[0] = 2; + } else if (keccak256(bytes(x)) == keccak256(bytes('00'))){ + xy[0] = 1; + } else { + xy[0] = 3; + } + + if (keccak256(bytes(y)) == keccak256(bytes('0'))){ + xy[1] = 2; + } else if (keccak256(bytes(y)) == keccak256(bytes('00'))){ + xy[1] = 1; + } else { + xy[1] = 3; + } + return xy[0] * xy[1]; + + } + + + + /** + * @dev See {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) override public view returns (string memory) { + + string memory ringCount = generateNum("ringCount", tokenId, hashOfBlock, 5, 15); + string memory scale = generateNum("scale", tokenId, hashOfBlock, 10, 40); + uint freq = getFrequency(tokenId); + string memory unr; + + // if unrevealed + if (keccak256(bytes(hashOfBlock)) == keccak256(bytes(''))) { scale = '0'; ringCount = '0'; unr = ' (Unrevealed)'; freq = 0;} + + string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Distortion #', toString(tokenId), unr, + '","attributes": [ { "trait_type": "Color", "value": "', + colorNames[generateColorNumber("color", tokenId)], + '" }, { "trait_type": "Distortion Scale", "value": ', + scale, + ' }, { "trait_type": "Rings", "value": ', + ringCount, + ' }, { "trait_type": "Frequency Multiple", "value": ', + toString(freq), + ' }]', + ', "description": "Distortion is a fully hand-typed 100% on-chain art collection limited to 1,000 pieces.", "image": "data:image/svg+xml;base64,', + Base64.encode(bytes(string(abi.encodePacked(generateDistortion(tokenId))))), + '"}')))); + string memory output = string(abi.encodePacked('data:application/json;base64,', json)); + + return output; + + } + + + /** + * @dev Converts a `uint256` to its ASCII `string` decimal representation. + */ + function toString(uint256 value) internal pure returns (string memory) { + // Inspired by OraclizeAPI's implementation - MIT licence + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } + + + function divide(uint a, uint b, uint precision) internal view returns ( uint) { + return a*(10**precision)/b; + } + + + /** + * @dev safety measure + */ + modifier callerIsWallet() { + require(tx.origin == msg.sender, "The caller is another contract"); + _; + } + + + function withdraw() public onlyOwner { + uint256 balance = address(this).balance; + Address.sendValue(payoutAddress, balance); + } + + + /** + * @notice For the attributes to be revealed, the hash of the block of the final mint must be set so provenance can be verified. + * + */ + function getHash() public view returns (string memory) { + return hashOfBlock; + } + + +} + + +/// [MIT License] +/// @title Base64 +/// @notice Provides a function for encoding some bytes in base64 +/// @author Brecht Devos +library Base64 { + bytes internal constant TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + /// @notice Encodes some bytes to the base64 representation + function encode(bytes memory data) internal pure returns (string memory) { + uint256 len = data.length; + if (len == 0) return ""; + + // multiply by 4/3 rounded up + uint256 encodedLen = 4 * ((len + 2) / 3); + + // Add some extra buffer at the end + bytes memory result = new bytes(encodedLen + 32); + + bytes memory table = TABLE; + + assembly { + let tablePtr := add(table, 1) + let resultPtr := add(result, 32) + + for { + let i := 0 + } lt(i, len) { + + } { + i := add(i, 3) + let input := and(mload(add(data, i)), 0xffffff) + + let out := mload(add(tablePtr, and(shr(18, input), 0x3F))) + out := shl(8, out) + out := add(out, and(mload(add(tablePtr, and(shr(12, input), 0x3F))), 0xFF)) + out := shl(8, out) + out := add(out, and(mload(add(tablePtr, and(shr(6, input), 0x3F))), 0xFF)) + out := shl(8, out) + out := add(out, and(mload(add(tablePtr, and(input, 0x3F))), 0xFF)) + out := shl(224, out) + + mstore(resultPtr, out) + + resultPtr := add(resultPtr, 4) + } + + switch mod(len, 3) + case 1 { + mstore(sub(resultPtr, 2), shl(240, 0x3d3d)) + } + case 2 { + mstore(sub(resultPtr, 1), shl(248, 0x3d)) + } + + mstore(result, encodedLen) + } + + return string(result); + } +} \ No newline at end of file diff --git a/semgrep-rules/security/erc721-arbitrary-transferfrom.yaml b/semgrep-rules/security/erc721-arbitrary-transferfrom.yaml new file mode 100644 index 0000000..902d3cf --- /dev/null +++ b/semgrep-rules/security/erc721-arbitrary-transferfrom.yaml @@ -0,0 +1,42 @@ +rules: + - + id: erc721-arbitrary-transferfrom + message: Custom ERC721 implementation lacks access control checks in _transfer() + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: MEDIUM + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/BlockSecAlert/status/1516289618605654024 + - https://etherscan.io/address/0xf3821adaceb6500c0a202971aecf840a033f236b + patterns: + - pattern-inside: | + function _transfer(...) { + ... + } + - pattern-inside: | + require(prevOwnership.addr == $FROM, ...); + ... + - pattern-not-inside: | + (<... _msgSender() == $FROM ...>); + ... + - pattern-not-inside: | + (<... _msgSender() == $PREV.$ADDR ...>); + ... + - pattern-not-inside: | + (<... msg.sender == $FROM ...>); + ... + - pattern-not-inside: | + require(_isApprovedOrOwner(...), ...); + ... + - pattern: _approve(...); + languages: + - solidity + severity: WARNING + diff --git a/semgrep-rules/security/erc721-reentrancy.sol b/semgrep-rules/security/erc721-reentrancy.sol new file mode 100644 index 0000000..6895303 --- /dev/null +++ b/semgrep-rules/security/erc721-reentrancy.sol @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./IERC721.sol"; +import "./IERC721Receiver.sol"; +import "./extensions/IERC721Metadata.sol"; +import "../../utils/Address.sol"; +import "../../utils/Context.sol"; +import "../../utils/Strings.sol"; +import "../../utils/introspection/ERC165.sol"; + +/** + * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including + * the Metadata extension, but not including the Enumerable extension, which is available separately as + * {ERC721Enumerable}. + */ +contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { + using Address for address; + using Strings for uint256; + + // Token name + string private _name; + + // Token symbol + string private _symbol; + + // Mapping from token ID to owner address + mapping(uint256 => address) private _owners; + + // Mapping owner address to token count + mapping(address => uint256) private _balances; + + // Mapping from token ID to approved address + mapping(uint256 => address) private _tokenApprovals; + + // Mapping from owner to operator approvals + mapping(address => mapping(address => bool)) private _operatorApprovals; + + /** + * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return + interfaceId == type(IERC721).interfaceId || + interfaceId == type(IERC721Metadata).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC721-balanceOf}. + */ + function balanceOf(address owner) public view virtual override returns (uint256) { + require(owner != address(0), "ERC721: balance query for the zero address"); + return _balances[owner]; + } + + /** + * @dev See {IERC721-ownerOf}. + */ + function ownerOf(uint256 tokenId) public view virtual override returns (address) { + address owner = _owners[tokenId]; + require(owner != address(0), "ERC721: owner query for nonexistent token"); + return owner; + } + + /** + * @dev See {IERC721Metadata-name}. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev See {IERC721Metadata-symbol}. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev See {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { + require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); + + string memory baseURI = _baseURI(); + return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""; + } + + /** + * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each + * token will be the concatenation of the `baseURI` and the `tokenId`. Empty + * by default, can be overriden in child contracts. + */ + function _baseURI() internal view virtual returns (string memory) { + return ""; + } + + /** + * @dev See {IERC721-approve}. + */ + function approve(address to, uint256 tokenId) public virtual override { + address owner = ERC721.ownerOf(tokenId); + require(to != owner, "ERC721: approval to current owner"); + + require( + _msgSender() == owner || isApprovedForAll(owner, _msgSender()), + "ERC721: approve caller is not owner nor approved for all" + ); + + _approve(to, tokenId); + } + + /** + * @dev See {IERC721-getApproved}. + */ + function getApproved(uint256 tokenId) public view virtual override returns (address) { + require(_exists(tokenId), "ERC721: approved query for nonexistent token"); + + return _tokenApprovals[tokenId]; + } + + /** + * @dev See {IERC721-setApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) public virtual override { + require(operator != _msgSender(), "ERC721: approve to caller"); + + _operatorApprovals[_msgSender()][operator] = approved; + emit ApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev See {IERC721-isApprovedForAll}. + */ + function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { + return _operatorApprovals[owner][operator]; + } + + /** + * @dev See {IERC721-transferFrom}. + */ + function transferFrom( + address from, + address to, + uint256 tokenId + ) public virtual override { + //solhint-disable-next-line max-line-length + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); + + _transfer(from, to, tokenId); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) public virtual override { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) public virtual override { + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); + _safeTransfer(from, to, tokenId, _data); + } + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients + * are aware of the ERC721 protocol to prevent tokens from being forever locked. + * + * `_data` is additional data, it has no specified format and it is sent in call to `to`. + * + * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. + * implement alternative mechanisms to perform token transfer, such as signature-based. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeTransfer( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) internal virtual { + _transfer(from, to, tokenId); + // ruleid: erc721-reentrancy + require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); + } + + /** + * @dev Returns whether `tokenId` exists. + * + * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. + * + * Tokens start existing when they are minted (`_mint`), + * and stop existing when they are burned (`_burn`). + */ + function _exists(uint256 tokenId) internal view virtual returns (bool) { + return _owners[tokenId] != address(0); + } + + /** + * @dev Returns whether `spender` is allowed to manage `tokenId`. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { + require(_exists(tokenId), "ERC721: operator query for nonexistent token"); + address owner = ERC721.ownerOf(tokenId); + return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); + } + + /** + * @dev Safely mints `tokenId` and transfers it to `to`. + * + * Requirements: + * + * - `tokenId` must not exist. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeMint(address to, uint256 tokenId) internal virtual { + _safeMint(to, tokenId, ""); + } + + /** + * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is + * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + */ + function _safeMint( + address to, + uint256 tokenId, + bytes memory _data + ) internal virtual { + _mint(to, tokenId); + require( + // ruleid: erc721-reentrancy + _checkOnERC721Received(address(0), to, tokenId, _data), + "ERC721: transfer to non ERC721Receiver implementer" + ); + } + + /** + * @dev Mints `tokenId` and transfers it to `to`. + * + * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible + * + * Requirements: + * + * - `tokenId` must not exist. + * - `to` cannot be the zero address. + * + * Emits a {Transfer} event. + */ + function _mint(address to, uint256 tokenId) internal virtual { + require(to != address(0), "ERC721: mint to the zero address"); + require(!_exists(tokenId), "ERC721: token already minted"); + + _beforeTokenTransfer(address(0), to, tokenId); + + _balances[to] += 1; + _owners[tokenId] = to; + + emit Transfer(address(0), to, tokenId); + } + + /** + * @dev Destroys `tokenId`. + * The approval is cleared when the token is burned. + * + * Requirements: + * + * - `tokenId` must exist. + * + * Emits a {Transfer} event. + */ + function _burn(uint256 tokenId) internal virtual { + address owner = ERC721.ownerOf(tokenId); + + _beforeTokenTransfer(owner, address(0), tokenId); + + // Clear approvals + _approve(address(0), tokenId); + + _balances[owner] -= 1; + delete _owners[tokenId]; + + emit Transfer(owner, address(0), tokenId); + } + + /** + * @dev Transfers `tokenId` from `from` to `to`. + * As opposed to {transferFrom}, this imposes no restrictions on msg.sender. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * + * Emits a {Transfer} event. + */ + function _transfer( + address from, + address to, + uint256 tokenId + ) internal virtual { + require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); + require(to != address(0), "ERC721: transfer to the zero address"); + + _beforeTokenTransfer(from, to, tokenId); + + // Clear approvals from the previous owner + _approve(address(0), tokenId); + + _balances[from] -= 1; + _balances[to] += 1; + _owners[tokenId] = to; + + emit Transfer(from, to, tokenId); + } + + /** + * @dev Approve `to` to operate on `tokenId` + * + * Emits a {Approval} event. + */ + function _approve(address to, uint256 tokenId) internal virtual { + _tokenApprovals[tokenId] = to; + emit Approval(ERC721.ownerOf(tokenId), to, tokenId); + } + + /** + * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address. + * The call is not executed if the target address is not a contract. + * + * @param from address representing the previous owner of the given token ID + * @param to target address that will receive the tokens + * @param tokenId uint256 ID of the token to be transferred + * @param _data bytes optional data to send along with the call + * @return bool whether the call correctly returned the expected magic value + */ + function _checkOnERC721Received( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) private returns (bool) { + if (to.isContract()) { + try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) { + return retval == IERC721Receiver.onERC721Received.selector; + } catch (bytes memory reason) { + if (reason.length == 0) { + revert("ERC721: transfer to non ERC721Receiver implementer"); + } else { + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } else { + return true; + } + } + + /** + * @dev Hook that is called before any token transfer. This includes minting + * and burning. + * + * Calling conditions: + * + * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be + * transferred to `to`. + * - When `from` is zero, `tokenId` will be minted for `to`. + * - When `to` is zero, ``from``'s `tokenId` 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 tokenId + ) internal virtual {} +} \ No newline at end of file diff --git a/semgrep-rules/security/erc721-reentrancy.yaml b/semgrep-rules/security/erc721-reentrancy.yaml new file mode 100644 index 0000000..61b6ca2 --- /dev/null +++ b/semgrep-rules/security/erc721-reentrancy.yaml @@ -0,0 +1,23 @@ +rules: + - + id: erc721-reentrancy + message: ERC721 onERC721Received() reentrancy + metadata: + category: security + technology: + - solidity + cwe: "CWE-841: Improper Enforcement of Behavioral Workflow" + confidence: HIGH + likelihood: LOW + impact: HIGH + subcategory: + - vuln + references: + - https://blocksecteam.medium.com/when-safemint-becomes-unsafe-lessons-from-the-hypebears-security-incident-2965209bda2a + - https://etherscan.io/address/0x14e0a1f310e2b7e321c91f58847e98b8c802f6ef + patterns: + - pattern: _checkOnERC721Received(...) + languages: + - solidity + severity: WARNING + diff --git a/semgrep-rules/security/erc777-reentrancy.sol b/semgrep-rules/security/erc777-reentrancy.sol new file mode 100644 index 0000000..1b6a506 --- /dev/null +++ b/semgrep-rules/security/erc777-reentrancy.sol @@ -0,0 +1,551 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC777/ERC777.sol) + +pragma solidity ^0.8.0; + +import "./IERC777Upgradeable.sol"; +import "./IERC777RecipientUpgradeable.sol"; +import "./IERC777SenderUpgradeable.sol"; +import "../ERC20/IERC20Upgradeable.sol"; +import "../../utils/AddressUpgradeable.sol"; +import "../../utils/ContextUpgradeable.sol"; +import "../../utils/introspection/IERC1820RegistryUpgradeable.sol"; +import "../../proxy/utils/Initializable.sol"; + +/** + * @dev Implementation of the {IERC777} 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}. + * + * Support for ERC20 is included in this contract, as specified by the EIP: both + * the ERC777 and ERC20 interfaces can be safely used when interacting with it. + * Both {IERC777-Sent} and {IERC20-Transfer} events are emitted on token + * movements. + * + * Additionally, the {IERC777-granularity} value is hard-coded to `1`, meaning that there + * are no special restrictions in the amount of tokens that created, moved, or + * destroyed. This makes integration with ERC20 applications seamless. + */ +contract ERC777Upgradeable is Initializable, ContextUpgradeable, IERC777Upgradeable, IERC20Upgradeable { + using AddressUpgradeable for address; + + IERC1820RegistryUpgradeable internal constant _ERC1820_REGISTRY = IERC1820RegistryUpgradeable(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); + + mapping(address => uint256) private _balances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + bytes32 private constant _TOKENS_SENDER_INTERFACE_HASH = keccak256("ERC777TokensSender"); + bytes32 private constant _TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient"); + + // This isn't ever read from - it's only used to respond to the defaultOperators query. + address[] private _defaultOperatorsArray; + + // Immutable, but accounts may revoke them (tracked in __revokedDefaultOperators). + mapping(address => bool) private _defaultOperators; + + // For each account, a mapping of its operators and revoked default operators. + mapping(address => mapping(address => bool)) private _operators; + mapping(address => mapping(address => bool)) private _revokedDefaultOperators; + + // ERC20-allowances + mapping(address => mapping(address => uint256)) private _allowances; + + /** + * @dev `defaultOperators` may be an empty array. + */ + function __ERC777_init( + string memory name_, + string memory symbol_, + address[] memory defaultOperators_ + ) internal onlyInitializing { + __Context_init_unchained(); + __ERC777_init_unchained(name_, symbol_, defaultOperators_); + } + + function __ERC777_init_unchained( + string memory name_, + string memory symbol_, + address[] memory defaultOperators_ + ) internal onlyInitializing { + _name = name_; + _symbol = symbol_; + + _defaultOperatorsArray = defaultOperators_; + for (uint256 i = 0; i < defaultOperators_.length; i++) { + _defaultOperators[defaultOperators_[i]] = true; + } + + // register interfaces + _ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256("ERC777Token"), address(this)); + _ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256("ERC20Token"), address(this)); + } + + /** + * @dev See {IERC777-name}. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev See {IERC777-symbol}. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev See {ERC20-decimals}. + * + * Always returns 18, as per the + * [ERC777 EIP](https://eips.ethereum.org/EIPS/eip-777#backward-compatibility). + */ + function decimals() public pure virtual returns (uint8) { + return 18; + } + + /** + * @dev See {IERC777-granularity}. + * + * This implementation always returns `1`. + */ + function granularity() public view virtual override returns (uint256) { + return 1; + } + + /** + * @dev See {IERC777-totalSupply}. + */ + function totalSupply() public view virtual override(IERC20Upgradeable, IERC777Upgradeable) returns (uint256) { + return _totalSupply; + } + + /** + * @dev Returns the amount of tokens owned by an account (`tokenHolder`). + */ + function balanceOf(address tokenHolder) public view virtual override(IERC20Upgradeable, IERC777Upgradeable) returns (uint256) { + return _balances[tokenHolder]; + } + + /** + * @dev See {IERC777-send}. + * + * Also emits a {IERC20-Transfer} event for ERC20 compatibility. + */ + function send( + address recipient, + uint256 amount, + bytes memory data + ) public virtual override { + _send(_msgSender(), recipient, amount, data, "", true); + } + + /** + * @dev See {IERC20-transfer}. + * + * Unlike `send`, `recipient` is _not_ required to implement the {IERC777Recipient} + * interface if it is a contract. + * + * Also emits a {Sent} event. + */ + function transfer(address recipient, uint256 amount) public virtual override returns (bool) { + require(recipient != address(0), "ERC777: transfer to the zero address"); + + address from = _msgSender(); + + _callTokensToSend(from, from, recipient, amount, "", ""); + + _move(from, from, recipient, amount, "", ""); + + _callTokensReceived(from, from, recipient, amount, "", "", false); + + return true; + } + + /** + * @dev See {IERC777-burn}. + * + * Also emits a {IERC20-Transfer} event for ERC20 compatibility. + */ + function burn(uint256 amount, bytes memory data) public virtual override { + _burn(_msgSender(), amount, data, ""); + } + + /** + * @dev See {IERC777-isOperatorFor}. + */ + function isOperatorFor(address operator, address tokenHolder) public view virtual override returns (bool) { + return + operator == tokenHolder || + (_defaultOperators[operator] && !_revokedDefaultOperators[tokenHolder][operator]) || + _operators[tokenHolder][operator]; + } + + /** + * @dev See {IERC777-authorizeOperator}. + */ + function authorizeOperator(address operator) public virtual override { + require(_msgSender() != operator, "ERC777: authorizing self as operator"); + + if (_defaultOperators[operator]) { + delete _revokedDefaultOperators[_msgSender()][operator]; + } else { + _operators[_msgSender()][operator] = true; + } + + emit AuthorizedOperator(operator, _msgSender()); + } + + /** + * @dev See {IERC777-revokeOperator}. + */ + function revokeOperator(address operator) public virtual override { + require(operator != _msgSender(), "ERC777: revoking self as operator"); + + if (_defaultOperators[operator]) { + _revokedDefaultOperators[_msgSender()][operator] = true; + } else { + delete _operators[_msgSender()][operator]; + } + + emit RevokedOperator(operator, _msgSender()); + } + + /** + * @dev See {IERC777-defaultOperators}. + */ + function defaultOperators() public view virtual override returns (address[] memory) { + return _defaultOperatorsArray; + } + + /** + * @dev See {IERC777-operatorSend}. + * + * Emits {Sent} and {IERC20-Transfer} events. + */ + function operatorSend( + address sender, + address recipient, + uint256 amount, + bytes memory data, + bytes memory operatorData + ) public virtual override { + require(isOperatorFor(_msgSender(), sender), "ERC777: caller is not an operator for holder"); + _send(sender, recipient, amount, data, operatorData, true); + } + + /** + * @dev See {IERC777-operatorBurn}. + * + * Emits {Burned} and {IERC20-Transfer} events. + */ + function operatorBurn( + address account, + uint256 amount, + bytes memory data, + bytes memory operatorData + ) public virtual override { + require(isOperatorFor(_msgSender(), account), "ERC777: caller is not an operator for holder"); + _burn(account, amount, data, operatorData); + } + + /** + * @dev See {IERC20-allowance}. + * + * Note that operator and allowance concepts are orthogonal: operators may + * not have allowance, and accounts with allowance may not be operators + * themselves. + */ + function allowance(address holder, address spender) public view virtual override returns (uint256) { + return _allowances[holder][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * Note that accounts cannot have allowance issued by their operators. + */ + function approve(address spender, uint256 value) public virtual override returns (bool) { + address holder = _msgSender(); + _approve(holder, spender, value); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Note that operator and allowance concepts are orthogonal: operators cannot + * call `transferFrom` (unless they have allowance), and accounts with + * allowance cannot call `operatorSend` (unless they are operators). + * + * Emits {Sent}, {IERC20-Transfer} and {IERC20-Approval} events. + */ + function transferFrom( + address holder, + address recipient, + uint256 amount + ) public virtual override returns (bool) { + require(recipient != address(0), "ERC777: transfer to the zero address"); + require(holder != address(0), "ERC777: transfer from the zero address"); + + address spender = _msgSender(); + + _callTokensToSend(spender, holder, recipient, amount, "", ""); + + _move(spender, holder, recipient, amount, "", ""); + + uint256 currentAllowance = _allowances[holder][spender]; + require(currentAllowance >= amount, "ERC777: transfer amount exceeds allowance"); + _approve(holder, spender, currentAllowance - amount); + + _callTokensReceived(spender, holder, recipient, amount, "", "", false); + + return true; + } + + /** + * @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * If a send hook is registered for `account`, the corresponding function + * will be called with `operator`, `data` and `operatorData`. + * + * See {IERC777Sender} and {IERC777Recipient}. + * + * Emits {Minted} and {IERC20-Transfer} events. + * + * Requirements + * + * - `account` cannot be the zero address. + * - if `account` is a contract, it must implement the {IERC777Recipient} + * interface. + */ + function _mint( + address account, + uint256 amount, + bytes memory userData, + bytes memory operatorData + ) internal virtual { + _mint(account, amount, userData, operatorData, true); + } + + /** + * @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * If `requireReceptionAck` is set to true, and if a send hook is + * registered for `account`, the corresponding function will be called with + * `operator`, `data` and `operatorData`. + * + * See {IERC777Sender} and {IERC777Recipient}. + * + * Emits {Minted} and {IERC20-Transfer} events. + * + * Requirements + * + * - `account` cannot be the zero address. + * - if `account` is a contract, it must implement the {IERC777Recipient} + * interface. + */ + function _mint( + address account, + uint256 amount, + bytes memory userData, + bytes memory operatorData, + bool requireReceptionAck + ) internal virtual { + require(account != address(0), "ERC777: mint to the zero address"); + + address operator = _msgSender(); + + _beforeTokenTransfer(operator, address(0), account, amount); + + // Update state variables + _totalSupply += amount; + _balances[account] += amount; + + _callTokensReceived(operator, address(0), account, amount, userData, operatorData, requireReceptionAck); + + emit Minted(operator, account, amount, userData, operatorData); + emit Transfer(address(0), account, amount); + } + + /** + * @dev Send tokens + * @param from address token holder address + * @param to address recipient address + * @param amount uint256 amount of tokens to transfer + * @param userData bytes extra information provided by the token holder (if any) + * @param operatorData bytes extra information provided by the operator (if any) + * @param requireReceptionAck if true, contract recipients are required to implement ERC777TokensRecipient + */ + function _send( + address from, + address to, + uint256 amount, + bytes memory userData, + bytes memory operatorData, + bool requireReceptionAck + ) internal virtual { + require(from != address(0), "ERC777: send from the zero address"); + require(to != address(0), "ERC777: send to the zero address"); + + address operator = _msgSender(); + + _callTokensToSend(operator, from, to, amount, userData, operatorData); + + _move(operator, from, to, amount, userData, operatorData); + + _callTokensReceived(operator, from, to, amount, userData, operatorData, requireReceptionAck); + } + + /** + * @dev Burn tokens + * @param from address token holder address + * @param amount uint256 amount of tokens to burn + * @param data bytes extra information provided by the token holder + * @param operatorData bytes extra information provided by the operator (if any) + */ + function _burn( + address from, + uint256 amount, + bytes memory data, + bytes memory operatorData + ) internal virtual { + require(from != address(0), "ERC777: burn from the zero address"); + + address operator = _msgSender(); + + _callTokensToSend(operator, from, address(0), amount, data, operatorData); + + _beforeTokenTransfer(operator, from, address(0), amount); + + // Update state variables + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC777: burn amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + } + _totalSupply -= amount; + + emit Burned(operator, from, amount, data, operatorData); + emit Transfer(from, address(0), amount); + } + + function _move( + address operator, + address from, + address to, + uint256 amount, + bytes memory userData, + bytes memory operatorData + ) private { + _beforeTokenTransfer(operator, from, to, amount); + + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC777: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + } + _balances[to] += amount; + + emit Sent(operator, from, to, amount, userData, operatorData); + emit Transfer(from, to, amount); + } + + /** + * @dev See {ERC20-_approve}. + * + * Note that accounts cannot have allowance issued by their operators. + */ + function _approve( + address holder, + address spender, + uint256 value + ) internal { + require(holder != address(0), "ERC777: approve from the zero address"); + require(spender != address(0), "ERC777: approve to the zero address"); + + _allowances[holder][spender] = value; + emit Approval(holder, spender, value); + } + + /** + * @dev Call from.tokensToSend() if the interface is registered + * @param operator address operator requesting the transfer + * @param from address token holder address + * @param to address recipient address + * @param amount uint256 amount of tokens to transfer + * @param userData bytes extra information provided by the token holder (if any) + * @param operatorData bytes extra information provided by the operator (if any) + */ + function _callTokensToSend( + address operator, + address from, + address to, + uint256 amount, + bytes memory userData, + bytes memory operatorData + ) private { + address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(from, _TOKENS_SENDER_INTERFACE_HASH); + if (implementer != address(0)) { + IERC777SenderUpgradeable(implementer).tokensToSend(operator, from, to, amount, userData, operatorData); + } + } + + /** + * @dev Call to.tokensReceived() if the interface is registered. Reverts if the recipient is a contract but + * tokensReceived() was not registered for the recipient + * @param operator address operator requesting the transfer + * @param from address token holder address + * @param to address recipient address + * @param amount uint256 amount of tokens to transfer + * @param userData bytes extra information provided by the token holder (if any) + * @param operatorData bytes extra information provided by the operator (if any) + * @param requireReceptionAck if true, contract recipients are required to implement ERC777TokensRecipient + */ + function _callTokensReceived( + address operator, + address from, + address to, + uint256 amount, + bytes memory userData, + bytes memory operatorData, + bool requireReceptionAck + ) private { + address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(to, _TOKENS_RECIPIENT_INTERFACE_HASH); + if (implementer != address(0)) { + // ruleid: erc777-reentrancy + IERC777RecipientUpgradeable(implementer).tokensReceived(operator, from, to, amount, userData, operatorData); + } else if (requireReceptionAck) { + require(!to.isContract(), "ERC777: token recipient contract has no implementer for ERC777TokensRecipient"); + } + } + + /** + * @dev Hook that is called before any token transfer. This includes + * calls to {send}, {transfer}, {operatorSend}, 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 operator, + address from, + address to, + uint256 amount + ) internal virtual {} + uint256[41] private __gap; +} \ No newline at end of file diff --git a/semgrep-rules/security/erc777-reentrancy.yaml b/semgrep-rules/security/erc777-reentrancy.yaml new file mode 100644 index 0000000..3de12a0 --- /dev/null +++ b/semgrep-rules/security/erc777-reentrancy.yaml @@ -0,0 +1,23 @@ +rules: + - + id: erc777-reentrancy + message: ERC777 tokensReceived() reentrancy + metadata: + category: security + technology: + - solidity + cwe: "CWE-841: Improper Enforcement of Behavioral Workflow" + confidence: HIGH + likelihood: LOW + impact: HIGH + subcategory: + - vuln + references: + - https://mirror.xyz/baconcoin.eth/LHaPiX38mnx8eJ2RVKNXHttHfweQMKNGmEnX4KUksk0 + - https://etherscan.io/address/0xf53f00f844b381963a47fde3325011566870b31f + patterns: + - pattern: $X.tokensReceived(...); + languages: + - solidity + severity: WARNING + diff --git a/semgrep-rules/security/exact-balance-check.sol b/semgrep-rules/security/exact-balance-check.sol new file mode 100644 index 0000000..596e86a --- /dev/null +++ b/semgrep-rules/security/exact-balance-check.sol @@ -0,0 +1,79 @@ +contract Foobar { + function doit1(address ext) { + uint256 bal = IERC20(ext).balanceOf(address(this)); + // ok: exact-balance-check + bal == 1338; + require( + 1==1 && + // ruleid: exact-balance-check + (bal == 1337) || + 1==2, + "Wrong balance!" + ); + // do smth + } + + function doit2(address ext) { + require( + 1==1 && + // ruleid: exact-balance-check + (address(ext).balance == 1337) || + 1==2, + "Wrong balance!" + ); + // do smth + } + + function doit3(address ext) { + require( + 1==1 && + // ruleid: exact-balance-check + (1337 == address(ext).balance) || + 1==2, + "Wrong balance!" + ); + // do smth + } + + function doit4(address ext) { + // ruleid: exact-balance-check + if (address(ext).balance == 1337 && 1 == 1) { + // do smth + uint a = 123; + }; + // do smth + } + + function doit5(address ext) { + // ok: exact-balance-check + if (1==1) { + // ruleid: exact-balance-check + bool b = address(ext).balance == 1337; + if(b) {} + }; + // do smth + } + + function doit_safe(address ext) { + require( + 1==1 && + // ok: exact-balance-check + (IERC20(ext).balanceOf(address(this)) >= 1337) || + 1==2, + "Wrong balance!" + ); + // do smth + } + + function doit2_safe(address ext) { + require( + 1==1 && + // ok: exact-balance-check + (address(ext).balance <= 1337) || + 1==2, + "Wrong balance!" + ); + // do smth + } + +} \ No newline at end of file diff --git a/semgrep-rules/security/exact-balance-check.yaml b/semgrep-rules/security/exact-balance-check.yaml new file mode 100644 index 0000000..29abbb7 --- /dev/null +++ b/semgrep-rules/security/exact-balance-check.yaml @@ -0,0 +1,54 @@ +rules: + - id: exact-balance-check + message: Testing the balance of an account as a basis for some action has risks + associated with unexpected receipt of ether or another token, including + tokens deliberately transfered to cause such tests to fail, as an MEV + attack. + metadata: + category: security + technology: + - solidity + cwe: "CWE-667: Improper Locking" + confidence: LOW + likelihood: MEDIUM + impact: MEDIUM + subcategory: + - vuln + references: + - https://entethalliance.org/specs/ethtrust-sl/v1/#req-1-exact-balance-check + mode: taint + pattern-sinks: + - pattern-either: + - patterns: + - pattern-either: + - pattern: $X == ... + - pattern: ... == $X + - pattern-either: + - pattern-inside: require(...) + - pattern-inside: | + if (<... $X == ... ...>) { + ... + } + - pattern-inside: | + if (<... ... == $X ...>) { + ... + } + - patterns: + - pattern-either: + - pattern: $BOOL = <... ... == ... ...> + - pattern-either: + - pattern-inside: | + ... + require(<... $BOOL ...>) + - pattern-inside: | + ... + if (<... $BOOL ...>) { + ... + } + pattern-sources: + - pattern-either: + - pattern: $T.balanceOf($A) + - pattern: $T.balance + languages: + - solidity + severity: WARNING diff --git a/semgrep-rules/security/gearbox-tokens-path-confusion.sol b/semgrep-rules/security/gearbox-tokens-path-confusion.sol new file mode 100644 index 0000000..d0006fe --- /dev/null +++ b/semgrep-rules/security/gearbox-tokens-path-confusion.sol @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Holdings, 2021 +pragma solidity ^0.7.4; +pragma abicoder v2; + +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol"; +import {ISwapRouter} from "../integrations/uniswap/IUniswapV3.sol"; +import {BytesLib} from "../integrations/uniswap/BytesLib.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {ICreditFilter} from "../interfaces/ICreditFilter.sol"; +import {ICreditManager} from "../interfaces/ICreditManager.sol"; +import {CreditManager} from "../credit/CreditManager.sol"; +import {Errors} from "../libraries/helpers/Errors.sol"; + + +/// @title UniswapV3 Router adapter +contract UniswapV3Adapter is ISwapRouter, ReentrancyGuard { + using BytesLib for bytes; + using SafeMath for uint256; + + ICreditManager public creditManager; + ICreditFilter public creditFilter; + address public router; + + /// @dev The length of the bytes encoded address + uint256 private constant ADDR_SIZE = 20; + + /// @dev Constructor + /// @param _creditManager Address Credit manager + /// @param _router Address of ISwapRouter + constructor(address _creditManager, address _router) { + require( + _creditManager != address(0) && _router != address(0), + Errors.ZERO_ADDRESS_IS_NOT_ALLOWED + ); + creditManager = ICreditManager(_creditManager); + creditFilter = ICreditFilter(creditManager.creditFilter()); + router = _router; + } + + /// @notice Swaps `amountIn` of one token for as much as possible of another token + /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata + /// @return amountOut The amount of the received token + function exactInputSingle(ExactInputSingleParams calldata params) + external + payable + override + nonReentrant + returns (uint256 amountOut) + { + address creditAccount = creditManager.getCreditAccountOrRevert( + msg.sender + ); + + creditManager.provideCreditAccountAllowance( + creditAccount, + router, + params.tokenIn + ); + + ExactInputSingleParams memory paramsUpdate = params; + paramsUpdate.recipient = creditAccount; + + // 0x414bf389 = exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160)) + bytes memory data = abi.encodeWithSelector( + bytes4(0x414bf389), // + + paramsUpdate + ); + + uint256 balanceInBefore = IERC20(paramsUpdate.tokenIn).balanceOf( + creditAccount + ); + + uint256 balanceOutBefore = IERC20(paramsUpdate.tokenOut).balanceOf( + creditAccount + ); + + (amountOut) = abi.decode( + creditManager.executeOrder(msg.sender, router, data), + (uint256) + ); + + creditFilter.checkCollateralChange( + creditAccount, + params.tokenIn, + params.tokenOut, + balanceInBefore.sub( + IERC20(paramsUpdate.tokenIn).balanceOf(creditAccount) + ), + IERC20(paramsUpdate.tokenOut).balanceOf(creditAccount).sub( + balanceOutBefore + ) + ); + } + + /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path + /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata + /// @return amountOut The amount of the received token + function exactInput(ExactInputParams calldata params) + external + payable + override + nonReentrant + returns (uint256 amountOut) + { + address creditAccount = creditManager.getCreditAccountOrRevert( + msg.sender + ); + + (address tokenIn, address tokenOut) = _extractTokens(params.path); + + creditManager.provideCreditAccountAllowance( + creditAccount, + router, + tokenIn + ); + + ExactInputParams memory paramsUpdate = params; + paramsUpdate.recipient = creditAccount; + + uint256 balanceInBefore = IERC20(tokenIn).balanceOf(creditAccount); + uint256 balanceOutBefore = IERC20(tokenOut).balanceOf(creditAccount); + + { + // 0xc04b8d59 = exactInput((bytes,address,uint256,uint256,uint256)) + bytes memory data = abi.encodeWithSelector( + bytes4(0xc04b8d59), // + + paramsUpdate + ); + + (amountOut) = abi.decode( + creditManager.executeOrder(msg.sender, router, data), + (uint256) + ); + } + + creditFilter.checkCollateralChange( + creditAccount, + tokenIn, + tokenOut, + balanceInBefore.sub(IERC20(tokenIn).balanceOf(creditAccount)), + IERC20(tokenOut).balanceOf(creditAccount).sub(balanceOutBefore) + ); + } + + /// @notice Swaps as little as possible of one token for `amountOut` of another token + /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata + /// @return amountIn The amount of the input token + function exactOutputSingle(ExactOutputSingleParams calldata params) + external + payable + override + nonReentrant + returns (uint256 amountIn) + { + address creditAccount = creditManager.getCreditAccountOrRevert( + msg.sender + ); + + creditManager.provideCreditAccountAllowance( + creditAccount, + router, + params.tokenIn + ); + + ExactOutputSingleParams memory paramsUpdate = params; + paramsUpdate.recipient = creditAccount; + + // + bytes memory data = abi.encodeWithSelector( + bytes4(0xdb3e2198), //+ + paramsUpdate + ); + + uint256 balanceInBefore = IERC20(paramsUpdate.tokenIn).balanceOf( + creditAccount + ); + + uint256 balanceOutBefore = IERC20(paramsUpdate.tokenOut).balanceOf( + creditAccount + ); + + (amountIn) = abi.decode( + creditManager.executeOrder(msg.sender, router, data), + (uint256) + ); + + creditFilter.checkCollateralChange( + creditAccount, + params.tokenIn, + params.tokenOut, + balanceInBefore.sub( + IERC20(paramsUpdate.tokenIn).balanceOf(creditAccount) + ), + IERC20(paramsUpdate.tokenOut).balanceOf(creditAccount).sub( + balanceOutBefore + ) + ); + } + + /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed) + /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata + /// @return amountIn The amount of the input token + function exactOutput(ExactOutputParams calldata params) + external + payable + override + nonReentrant + returns (uint256 amountIn) + { + address creditAccount = creditManager.getCreditAccountOrRevert( + msg.sender + ); + + (address tokenOut, address tokenIn) = _extractTokens(params.path); + + creditManager.provideCreditAccountAllowance( + creditAccount, + router, + tokenIn + ); + + ExactOutputParams memory paramsUpdate = params; + paramsUpdate.recipient = creditAccount; + + uint256 balanceInBefore = IERC20(tokenIn).balanceOf(creditAccount); + uint256 balanceOutBefore = IERC20(tokenOut).balanceOf(creditAccount); + + { + bytes memory data = abi.encodeWithSelector( + bytes4(0xf28c0498), // exactOutput((bytes,address,uint256,uint256,uint256)) + paramsUpdate + ); + + (amountIn) = abi.decode( + creditManager.executeOrder(msg.sender, router, data), + (uint256) + ); + } + + creditFilter.checkCollateralChange( + creditAccount, + tokenIn, + tokenOut, + balanceInBefore.sub(IERC20(tokenIn).balanceOf(creditAccount)), + IERC20(tokenOut).balanceOf(creditAccount).sub(balanceOutBefore) + ); + } + + function _extractTokens(bytes memory path) + internal + pure + returns (address tokenA, address tokenB) + { + tokenA = path.toAddress(0); + // ruleid: gearbox-tokens-path-confusion + tokenB = path.toAddress(path.length - ADDR_SIZE); + } +} diff --git a/semgrep-rules/security/gearbox-tokens-path-confusion.yaml b/semgrep-rules/security/gearbox-tokens-path-confusion.yaml new file mode 100644 index 0000000..a131ff2 --- /dev/null +++ b/semgrep-rules/security/gearbox-tokens-path-confusion.yaml @@ -0,0 +1,23 @@ +rules: + - + id: gearbox-tokens-path-confusion + message: UniswapV3 adapter implemented incorrect extraction of path parameters + metadata: + category: security + technology: + - solidity + cwe: "CWE-1285: Improper Validation of Specified Index, Position, or Offset in Input" + confidence: LOW + likelihood: LOW + impact: HIGH + subcategory: + - vuln + references: + - https://medium.com/@nnez/different-parsers-different-results-acecf84dfb0c + - https://etherscan.io/address/0xbA7B57D7E4d4A7516FC1CbfF1CA5182eBC0c1491 + patterns: + - pattern: $PATH.toAddress($PATH.length - $ADDR_SIZE); + languages: + - solidity + severity: WARNING + diff --git a/semgrep-rules/security/incorrect-use-of-blockhash.sol b/semgrep-rules/security/incorrect-use-of-blockhash.sol new file mode 100644 index 0000000..459563b --- /dev/null +++ b/semgrep-rules/security/incorrect-use-of-blockhash.sol @@ -0,0 +1,25 @@ +pragma solidity 0.8.0; + + +contract Test{ + function func1() external{ + //ruleid: incorrect-use-of-blockhash + bytes32 result1 = blockhash(block.number); + + //ruleid: incorrect-use-of-blockhash + bytes32 result2 = blockhash(block.number + 1); + + //ruleid: incorrect-use-of-blockhash + bytes32 result3 = blockhash(block.number * 2); + + //ruleid: incorrect-use-of-blockhash + bytes32 result4 = block.blockhash(block.number); + + //ok: incorrect-use-of-blockhash + bytes32 result5 = blockhash(block.number - 1); + + uint256 n = 123; + //ok: incorrect-use-of-blockhash + bytes32 result6 = blockhash(block.number - n); + } +} \ No newline at end of file diff --git a/semgrep-rules/security/incorrect-use-of-blockhash.yaml b/semgrep-rules/security/incorrect-use-of-blockhash.yaml new file mode 100644 index 0000000..67070d7 --- /dev/null +++ b/semgrep-rules/security/incorrect-use-of-blockhash.yaml @@ -0,0 +1,26 @@ +rules: + - id: incorrect-use-of-blockhash + message: blockhash(block.number) and blockhash(block.number + N) always returns 0. + metadata: + category: security + technology: + - solidity + cwe: "CWE-341: Predictable from Observable State" + confidence: HIGH + likelihood: LOW + impact: MEDIUM + subcategory: + - vuln + references: + - https://blog.positive.com/predicting-random-numbers-in-ethereum-smart-contracts-e5358c6b8620 + patterns: + - pattern-either: + - pattern: blockhash(block.number) + - pattern: blockhash(block.number + $N) + - pattern: blockhash(block.number * $N) + - pattern: block.blockhash(block.number) + - pattern: block.blockhash(block.number + $N) + - pattern: block.blockhash(block.number * $N) + severity: ERROR + languages: + - solidity diff --git a/semgrep-rules/security/keeper-network-oracle-manipulation.sol b/semgrep-rules/security/keeper-network-oracle-manipulation.sol new file mode 100644 index 0000000..63aebf4 --- /dev/null +++ b/semgrep-rules/security/keeper-network-oracle-manipulation.sol @@ -0,0 +1,41 @@ +pragma solidity ^0.5.16; + +import "./SafeMath.sol"; + +interface IFeed { + function decimals() external view returns (uint8); + function latestAnswer() external view returns (uint); +} + +interface IKeep3rV2 { + function current(address tokenIn, uint amountIn, address tokenOut) external view returns (uint256 amountOut, uint lastUpdatedAgo); +} + +contract InvFeed is IFeed { + using SafeMath for uint; + + IKeep3rV2 public keep3rV2Feed; + IFeed public ethFeed; + address public inv; + address public weth; + + constructor(IKeep3rV2 _keep3rV2Feed, IFeed _ethFeed, address _inv, address _weth) public { + keep3rV2Feed = _keep3rV2Feed; + ethFeed = _ethFeed; + inv = _inv; + weth = _weth; + } + + function decimals() public view returns(uint8) { + return 18; + } + + function latestAnswer() public view returns (uint) { + // ruleid: keeper-network-oracle-manipulation + (uint invEthPrice, ) = keep3rV2Feed.current(inv, 1e18, weth); + return invEthPrice + .mul(ethFeed.latestAnswer()) + .div(10**uint256(ethFeed.decimals())); + } + +} \ No newline at end of file diff --git a/semgrep-rules/security/keeper-network-oracle-manipulation.yaml b/semgrep-rules/security/keeper-network-oracle-manipulation.yaml new file mode 100644 index 0000000..dfd6d38 --- /dev/null +++ b/semgrep-rules/security/keeper-network-oracle-manipulation.yaml @@ -0,0 +1,28 @@ +rules: + - + id: keeper-network-oracle-manipulation + message: | + Keep3rV2.current() call has high data freshness, but it has low security, + an exploiter simply needs to manipulate 2 data points to be able to impact the feed. + metadata: + category: security + technology: + - solidity + cwe: "CWE-682: Incorrect Calculation" + confidence: HIGH + likelihood: LOW + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/peckshield/status/1510232640338608131 + - https://twitter.com/FrankResearcher/status/1510239094777032713 + - https://twitter.com/larry0x/status/1510263618180464644 + - https://andrecronje.medium.com/keep3r-network-on-chain-oracle-price-feeds-3c67ed002a9 + - https://etherscan.io/address/0x210ac53b27f16e20a9aa7d16260f84693390258f + patterns: + - pattern: $KEEPER.current($TOKENIN, $AMOUNTIN, $TOKENOUT); + languages: + - solidity + severity: WARNING + diff --git a/semgrep-rules/security/missing-assignment.sol b/semgrep-rules/security/missing-assignment.sol new file mode 100644 index 0000000..0b69775 --- /dev/null +++ b/semgrep-rules/security/missing-assignment.sol @@ -0,0 +1,53 @@ +contract Test { + // ok: missing-assignment + struct Kek { + // ok: missing-assignment + mapping(uint256 => uint256) lol; + } + + // ok: missing-assignment + function smth(uint256 _id) { + // ok: missing-assignment + Kek puk; + // ruleid: missing-assignment + puk.lol[_id]; + // ok: missing-assignment + uint256 haha = 123; + // ok: missing-assignment + haha = 321; + // ruleid: missing-assignment + haha; + // ruleid: missing-assignment + haha == 123; + // ok: missing-assignment + haha += 1; + // ok: missing-assignment + haha -= 1; + // ok: missing-assignment + haha++; + // ok: missing-assignment + haha--; + // ok: missing-assignment + --haha; + // ok: missing-assignment + ++haha; + // ok: missing-assignment + return haha; + } + + // ok: missing-assignment + function doit(uint256 a) {} + + function heh(uint256 _id) { + // ok: missing-assignment + Kek puk; + // ok: missing-assignment + doit(puk.lol[_id]); + // ok: missing-assignment + doit(puk.lol[_id]); + // ruleid: missing-assignment + "heh"; + // ok: missing-assignment + return; + } +} \ No newline at end of file diff --git a/semgrep-rules/security/missing-assignment.yaml b/semgrep-rules/security/missing-assignment.yaml new file mode 100644 index 0000000..0b437d2 --- /dev/null +++ b/semgrep-rules/security/missing-assignment.yaml @@ -0,0 +1,49 @@ +rules: + - id: missing-assignment + message: Meaningless statement that does not change any values could be a sign of missed security checks or other important changes. + metadata: + category: security + technology: + - solidity + cwe: "CWE-1164: Irrelevant Code" + confidence: HIGH + likelihood: HIGH + impact: MEDIUM + subcategory: + - vuln + patterns: + - pattern-either: + - pattern: | + $X; + - pattern: | + $X[$Y]; + - pattern: | + $X == $Y; + - pattern-not: $FUNC(...); + - pattern-not: $FUNC(); + - pattern-not: $VAR++; + - pattern-not: $VAR--; + - pattern-not: ++$VAR; + - pattern-not: --$VAR; + - pattern-not: ... = ...; + - pattern-not: ... += ...; + - pattern-not: ... -= ...; + - pattern-not: $TYPE $VAR; + - pattern-not: return $VAR; + - pattern-not: return; + - pattern-not: continue; + - pattern-not: break; + - pattern-not: _; + - pattern-not: error $NAME(); + - pattern-not: error $NAME($ARGS); + #- pattern-not: function $NAME($ARGS) virtual {} # doesn't work + - pattern-not: | # let's not match empty function bodies + {} + - pattern-not: | # let's not match empty constructors + constructor(...){} + - metavariable-regex: + metavariable: $X + regex: "^\\S+$" + languages: + - solidity + severity: WARNING diff --git a/semgrep-rules/security/msg-value-multicall.sol b/semgrep-rules/security/msg-value-multicall.sol new file mode 100644 index 0000000..a2a5d5f --- /dev/null +++ b/semgrep-rules/security/msg-value-multicall.sol @@ -0,0 +1,722 @@ +pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; + + +//---------------------------------------------------------------------------------- +// I n s t a n t +// +// .:mmm. .:mmm:. .ii. .:SSSSSSSSSSSSS. .oOOOOOOOOOOOo. +// .mMM'':Mm. .:MM'':Mm:. .II: :SSs.......... .oOO'''''''''''OOo. +// .:Mm' ':Mm. .:Mm' 'MM:. .II: 'sSSSSSSSSSSSSS:. :OO. .OO: +// .'mMm' ':MM:.:MMm' ':MM:. .II: .:...........:SS. 'OOo:.........:oOO' +// 'mMm' ':MMmm' 'mMm: II: 'sSSSSSSSSSSSSS' 'oOOOOOOOOOOOO' +// +//---------------------------------------------------------------------------------- +// +// Chef Gonpachi's Dutch Auction +// +// A declining price auction with fair price discovery. +// +// Inspired by DutchSwap's Dutch Auctions +// https://github.com/deepyr/DutchSwap +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// Made for Sushi.com +// +// Enjoy. (c) Chef Gonpachi, Kusatoshi, SSMikazu 2021 +// +// +// --------------------------------------------------------------------- +// SPDX-License-Identifier: GPL-3.0 +// --------------------------------------------------------------------- + +import "../OpenZeppelin/utils/ReentrancyGuard.sol"; +import "../Access/MISOAccessControls.sol"; +import "../Utils/SafeTransfer.sol"; +import "../Utils/BoringBatchable.sol"; +import "../Utils/BoringMath.sol"; +import "../Utils/BoringERC20.sol"; +import "../Utils/Documents.sol"; +import "../interfaces/IPointList.sol"; +import "../interfaces/IMisoMarket.sol"; + +/// @notice Attribution to delta.financial +/// @notice Attribution to dutchswap.com + +contract DutchAuction is IMisoMarket, MISOAccessControls, BoringBatchable, SafeTransfer, Documents , ReentrancyGuard { + using BoringMath for uint256; + using BoringMath128 for uint128; + using BoringMath64 for uint64; + using BoringERC20 for IERC20; + + /// @notice MISOMarket template id for the factory contract. + /// @dev For different marketplace types, this must be incremented. + uint256 public constant override marketTemplate = 2; + /// @dev The placeholder ETH address. + address private constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + /// @dev The multiplier for decimal precision + uint256 private constant MISO_PRECISION = 1e18; + + /// @notice Main market variables. + struct MarketInfo { + uint64 startTime; + uint64 endTime; + uint128 totalTokens; + } + MarketInfo public marketInfo; + + /// @notice Market price variables. + struct MarketPrice { + uint128 startPrice; + uint128 minimumPrice; + } + MarketPrice public marketPrice; + + /// @notice Market dynamic variables. + struct MarketStatus { + uint128 commitmentsTotal; + bool finalized; + bool usePointList; + } + + MarketStatus public marketStatus; + + /// @notice The token being sold. + address public auctionToken; + /// @notice The currency the auction accepts for payment. Can be ETH or token address. + address public paymentCurrency; + /// @notice Where the auction funds will get paid. + address payable public wallet; + /// @notice Address that manages auction approvals. + address public pointList; + + /// @notice The commited amount of accounts. + mapping(address => uint256) public commitments; + /// @notice Amount of tokens to claim per address. + mapping(address => uint256) public claimed; + + /// @notice Event for updating auction times. Needs to be before auction starts. + event AuctionTimeUpdated(uint256 startTime, uint256 endTime); + /// @notice Event for updating auction prices. Needs to be before auction starts. + event AuctionPriceUpdated(uint256 startPrice, uint256 minimumPrice); + /// @notice Event for updating auction wallet. Needs to be before auction starts. + event AuctionWalletUpdated(address wallet); + + /// @notice Event for adding a commitment. + event AddedCommitment(address addr, uint256 commitment); + /// @notice Event for finalization of the auction. + event AuctionFinalized(); + /// @notice Event for cancellation of the auction. + event AuctionCancelled(); + + /** + * @notice Initializes main contract variables and transfers funds for the auction. + * @dev Init function. + * @param _funder The address that funds the token for crowdsale. + * @param _token Address of the token being sold. + * @param _totalTokens The total number of tokens to sell in auction. + * @param _startTime Auction start time. + * @param _endTime Auction end time. + * @param _paymentCurrency The currency the crowdsale accepts for payment. Can be ETH or token address. + * @param _startPrice Starting price of the auction. + * @param _minimumPrice The minimum auction price. + * @param _admin Address that can finalize auction. + * @param _pointList Address that will manage auction approvals. + * @param _wallet Address where collected funds will be forwarded to. + */ + function initAuction( + address _funder, + address _token, + uint256 _totalTokens, + uint256 _startTime, + uint256 _endTime, + address _paymentCurrency, + uint256 _startPrice, + uint256 _minimumPrice, + address _admin, + address _pointList, + address payable _wallet + ) public { + require(_startTime < 10000000000, "DutchAuction: enter an unix timestamp in seconds, not miliseconds"); + require(_endTime < 10000000000, "DutchAuction: enter an unix timestamp in seconds, not miliseconds"); + require(_startTime >= block.timestamp, "DutchAuction: start time is before current time"); + require(_endTime > _startTime, "DutchAuction: end time must be older than start price"); + require(_totalTokens > 0,"DutchAuction: total tokens must be greater than zero"); + require(_startPrice > _minimumPrice, "DutchAuction: start price must be higher than minimum price"); + require(_minimumPrice > 0, "DutchAuction: minimum price must be greater than 0"); + require(_admin != address(0), "DutchAuction: admin is the zero address"); + require(_wallet != address(0), "DutchAuction: wallet is the zero address"); + require(IERC20(_token).decimals() == 18, "DutchAuction: Token does not have 18 decimals"); + if (_paymentCurrency != ETH_ADDRESS) { + require(IERC20(_paymentCurrency).decimals() > 0, "DutchAuction: Payment currency is not ERC20"); + } + + marketInfo.startTime = BoringMath.to64(_startTime); + marketInfo.endTime = BoringMath.to64(_endTime); + marketInfo.totalTokens = BoringMath.to128(_totalTokens); + + marketPrice.startPrice = BoringMath.to128(_startPrice); + marketPrice.minimumPrice = BoringMath.to128(_minimumPrice); + + auctionToken = _token; + paymentCurrency = _paymentCurrency; + wallet = _wallet; + + initAccessControls(_admin); + + _setList(_pointList); + _safeTransferFrom(_token, _funder, _totalTokens); + } + + + + /** + Dutch Auction Price Function + ============================ + + Start Price ----- + \ + \ + \ + \ ------------ Clearing Price + / \ = AmountRaised/TokenSupply + Token Price -- \ + / \ + -- ----------- Minimum Price + Amount raised / End Time + */ + + /** + * @notice Calculates the average price of each token from all commitments. + * @return Average token price. + */ + function tokenPrice() public view returns (uint256) { + return uint256(marketStatus.commitmentsTotal).mul(MISO_PRECISION) + .mul(1e18).div(uint256(marketInfo.totalTokens)).div(MISO_PRECISION); + } + + /** + * @notice Returns auction price in any time. + * @return Fixed start price or minimum price if outside of auction time, otherwise calculated current price. + */ + function priceFunction() public view returns (uint256) { + /// @dev Return Auction Price + if (block.timestamp <= uint256(marketInfo.startTime)) { + return uint256(marketPrice.startPrice); + } + if (block.timestamp >= uint256(marketInfo.endTime)) { + return uint256(marketPrice.minimumPrice); + } + + return _currentPrice(); + } + + /** + * @notice The current clearing price of the Dutch auction. + * @return The bigger from tokenPrice and priceFunction. + */ + function clearingPrice() public view returns (uint256) { + /// @dev If auction successful, return tokenPrice + if (tokenPrice() > priceFunction()) { + return tokenPrice(); + } + return priceFunction(); + } + + + ///-------------------------------------------------------- + /// Commit to buying tokens! + ///-------------------------------------------------------- + + receive() external payable { + revertBecauseUserDidNotProvideAgreement(); + } + + /** + * @dev Attribution to the awesome delta.financial contracts + */ + function marketParticipationAgreement() public pure returns (string memory) { + return "I understand that I'm interacting with a smart contract. I understand that tokens commited are subject to the token issuer and local laws where applicable. I reviewed code of the smart contract and understand it fully. I agree to not hold developers or other people associated with the project liable for any losses or misunderstandings"; + } + /** + * @dev Not using modifiers is a purposeful choice for code readability. + */ + function revertBecauseUserDidNotProvideAgreement() internal pure { + revert("No agreement provided, please review the smart contract before interacting with it"); + } + + /** + * @notice Checks the amount of ETH to commit and adds the commitment. Refunds the buyer if commit is too high. + * @param _beneficiary Auction participant ETH address. + */ + function commitEth( + address payable _beneficiary, + bool readAndAgreedToMarketParticipationAgreement + ) + public payable + { + require(paymentCurrency == ETH_ADDRESS, "DutchAuction: payment currency is not ETH address"); + if(readAndAgreedToMarketParticipationAgreement == false) { + revertBecauseUserDidNotProvideAgreement(); + } + // Get ETH able to be committed + // ruleid: msg-value-multicall + uint256 ethToTransfer = calculateCommitment(msg.value); + + /// @notice Accept ETH Payments. + // ruleid: msg-value-multicall + uint256 ethToRefund = msg.value.sub(ethToTransfer); + if (ethToTransfer > 0) { + _addCommitment(_beneficiary, ethToTransfer); + } + /// @notice Return any ETH to be refunded. + if (ethToRefund > 0) { + _beneficiary.transfer(ethToRefund); + } + } + + /** + * @notice Buy Tokens by commiting approved ERC20 tokens to this contract address. + * @param _amount Amount of tokens to commit. + */ + function commitTokens(uint256 _amount, bool readAndAgreedToMarketParticipationAgreement) public { + commitTokensFrom(msg.sender, _amount, readAndAgreedToMarketParticipationAgreement); + } + + + /** + * @notice Checks how much is user able to commit and processes that commitment. + * @dev Users must approve contract prior to committing tokens to auction. + * @param _from User ERC20 address. + * @param _amount Amount of approved ERC20 tokens. + */ + function commitTokensFrom( + address _from, + uint256 _amount, + bool readAndAgreedToMarketParticipationAgreement + ) + public nonReentrant + { + require(address(paymentCurrency) != ETH_ADDRESS, "DutchAuction: Payment currency is not a token"); + if(readAndAgreedToMarketParticipationAgreement == false) { + revertBecauseUserDidNotProvideAgreement(); + } + uint256 tokensToTransfer = calculateCommitment(_amount); + if (tokensToTransfer > 0) { + _safeTransferFrom(paymentCurrency, msg.sender, tokensToTransfer); + _addCommitment(_from, tokensToTransfer); + } + } + + /** + * @notice Calculates the pricedrop factor. + * @return Value calculated from auction start and end price difference divided the auction duration. + */ + function priceDrop() public view returns (uint256) { + MarketInfo memory _marketInfo = marketInfo; + MarketPrice memory _marketPrice = marketPrice; + + uint256 numerator = uint256(_marketPrice.startPrice.sub(_marketPrice.minimumPrice)); + uint256 denominator = uint256(_marketInfo.endTime.sub(_marketInfo.startTime)); + return numerator / denominator; + } + + + /** + * @notice How many tokens the user is able to claim. + * @param _user Auction participant address. + * @return claimerCommitment User commitments reduced by already claimed tokens. + */ + function tokensClaimable(address _user) public view returns (uint256 claimerCommitment) { + if (commitments[_user] == 0) return 0; + uint256 unclaimedTokens = IERC20(auctionToken).balanceOf(address(this)); + + claimerCommitment = commitments[_user].mul(uint256(marketInfo.totalTokens)).div(uint256(marketStatus.commitmentsTotal)); + claimerCommitment = claimerCommitment.sub(claimed[_user]); + + if(claimerCommitment > unclaimedTokens){ + claimerCommitment = unclaimedTokens; + } + } + + /** + * @notice Calculates total amount of tokens committed at current auction price. + * @return Number of tokens commited. + */ + function totalTokensCommitted() public view returns (uint256) { + return uint256(marketStatus.commitmentsTotal).mul(1e18).div(clearingPrice()); + } + + /** + * @notice Calculates the amout able to be committed during an auction. + * @param _commitment Commitment user would like to make. + * @return committed Amount allowed to commit. + */ + function calculateCommitment(uint256 _commitment) public view returns (uint256 committed) { + uint256 maxCommitment = uint256(marketInfo.totalTokens).mul(clearingPrice()).div(1e18); + if (uint256(marketStatus.commitmentsTotal).add(_commitment) > maxCommitment) { + return maxCommitment.sub(uint256(marketStatus.commitmentsTotal)); + } + return _commitment; + } + + /** + * @notice Checks if the auction is open. + * @return True if current time is greater than startTime and less than endTime. + */ + function isOpen() public view returns (bool) { + return block.timestamp >= uint256(marketInfo.startTime) && block.timestamp <= uint256(marketInfo.endTime); + } + + /** + * @notice Successful if tokens sold equals totalTokens. + * @return True if tokenPrice is bigger or equal clearingPrice. + */ + function auctionSuccessful() public view returns (bool) { + return tokenPrice() >= clearingPrice(); + } + + /** + * @notice Checks if the auction has ended. + * @return True if auction is successful or time has ended. + */ + function auctionEnded() public view returns (bool) { + return auctionSuccessful() || block.timestamp > uint256(marketInfo.endTime); + } + + /** + * @return Returns true if market has been finalized + */ + function finalized() public view returns (bool) { + return marketStatus.finalized; + } + + /** + * @return Returns true if 14 days have passed since the end of the auction + */ + function finalizeTimeExpired() public view returns (bool) { + return uint256(marketInfo.endTime) + 7 days < block.timestamp; + } + + /** + * @notice Calculates price during the auction. + * @return Current auction price. + */ + function _currentPrice() private view returns (uint256) { + uint256 priceDiff = block.timestamp.sub(uint256(marketInfo.startTime)).mul(priceDrop()); + return uint256(marketPrice.startPrice).sub(priceDiff); + } + + /** + * @notice Updates commitment for this address and total commitment of the auction. + * @param _addr Bidders address. + * @param _commitment The amount to commit. + */ + function _addCommitment(address _addr, uint256 _commitment) internal { + require(block.timestamp >= uint256(marketInfo.startTime) && block.timestamp <= uint256(marketInfo.endTime), "DutchAuction: outside auction hours"); + MarketStatus storage status = marketStatus; + + uint256 newCommitment = commitments[_addr].add(_commitment); + if (status.usePointList) { + require(IPointList(pointList).hasPoints(_addr, newCommitment)); + } + + commitments[_addr] = newCommitment; + status.commitmentsTotal = BoringMath.to128(uint256(status.commitmentsTotal).add(_commitment)); + emit AddedCommitment(_addr, _commitment); + } + + + //-------------------------------------------------------- + // Finalize Auction + //-------------------------------------------------------- + + + /** + * @notice Cancel Auction + * @dev Admin can cancel the auction before it starts + */ + function cancelAuction() public nonReentrant + { + require(hasAdminRole(msg.sender)); + MarketStatus storage status = marketStatus; + require(!status.finalized, "DutchAuction: auction already finalized"); + require( uint256(status.commitmentsTotal) == 0, "DutchAuction: auction already committed" ); + _safeTokenPayment(auctionToken, wallet, uint256(marketInfo.totalTokens)); + status.finalized = true; + emit AuctionCancelled(); + } + + /** + * @notice Auction finishes successfully above the reserve. + * @dev Transfer contract funds to initialized wallet. + */ + function finalize() public nonReentrant + { + + require(hasAdminRole(msg.sender) + || hasSmartContractRole(msg.sender) + || wallet == msg.sender + || finalizeTimeExpired(), "DutchAuction: sender must be an admin"); + MarketStatus storage status = marketStatus; + + require(!status.finalized, "DutchAuction: auction already finalized"); + if (auctionSuccessful()) { + /// @dev Successful auction + /// @dev Transfer contributed tokens to wallet. + _safeTokenPayment(paymentCurrency, wallet, uint256(status.commitmentsTotal)); + } else { + /// @dev Failed auction + /// @dev Return auction tokens back to wallet. + require(block.timestamp > uint256(marketInfo.endTime), "DutchAuction: auction has not finished yet"); + _safeTokenPayment(auctionToken, wallet, uint256(marketInfo.totalTokens)); + } + status.finalized = true; + emit AuctionFinalized(); + } + + + /// @notice Withdraws bought tokens, or returns commitment if the sale is unsuccessful. + function withdrawTokens() public { + withdrawTokens(msg.sender); + } + + /** + * @notice Withdraws bought tokens, or returns commitment if the sale is unsuccessful. + * @dev Withdraw tokens only after auction ends. + * @param beneficiary Whose tokens will be withdrawn. + */ + function withdrawTokens(address payable beneficiary) public nonReentrant { + if (auctionSuccessful()) { + require(marketStatus.finalized, "DutchAuction: not finalized"); + /// @dev Successful auction! Transfer claimed tokens. + uint256 tokensToClaim = tokensClaimable(beneficiary); + require(tokensToClaim > 0, "DutchAuction: No tokens to claim"); + claimed[beneficiary] = claimed[beneficiary].add(tokensToClaim); + _safeTokenPayment(auctionToken, beneficiary, tokensToClaim); + } else { + /// @dev Auction did not meet reserve price. + /// @dev Return committed funds back to user. + require(block.timestamp > uint256(marketInfo.endTime), "DutchAuction: auction has not finished yet"); + uint256 fundsCommitted = commitments[beneficiary]; + commitments[beneficiary] = 0; // Stop multiple withdrawals and free some gas + _safeTokenPayment(paymentCurrency, beneficiary, fundsCommitted); + } + } + + + //-------------------------------------------------------- + // Documents + //-------------------------------------------------------- + + function setDocument(string calldata _name, string calldata _data) external { + require(hasAdminRole(msg.sender) ); + _setDocument( _name, _data); + } + + function setDocuments(string[] calldata _name, string[] calldata _data) external { + require(hasAdminRole(msg.sender) ); + uint256 numDocs = _name.length; + for (uint256 i = 0; i < numDocs; i++) { + _setDocument( _name[i], _data[i]); + } + } + + function removeDocument(string calldata _name) external { + require(hasAdminRole(msg.sender)); + _removeDocument(_name); + } + + + //-------------------------------------------------------- + // Point Lists + //-------------------------------------------------------- + + + function setList(address _list) external { + require(hasAdminRole(msg.sender)); + _setList(_list); + } + + function enableList(bool _status) external { + require(hasAdminRole(msg.sender)); + marketStatus.usePointList = _status; + } + + function _setList(address _pointList) private { + if (_pointList != address(0)) { + pointList = _pointList; + marketStatus.usePointList = true; + } + } + + //-------------------------------------------------------- + // Setter Functions + //-------------------------------------------------------- + + /** + * @notice Admin can set start and end time through this function. + * @param _startTime Auction start time. + * @param _endTime Auction end time. + */ + function setAuctionTime(uint256 _startTime, uint256 _endTime) external { + require(hasAdminRole(msg.sender)); + require(_startTime < 10000000000, "DutchAuction: enter an unix timestamp in seconds, not miliseconds"); + require(_endTime < 10000000000, "DutchAuction: enter an unix timestamp in seconds, not miliseconds"); + require(_startTime >= block.timestamp, "DutchAuction: start time is before current time"); + require(_endTime > _startTime, "DutchAuction: end time must be older than start time"); + require(marketStatus.commitmentsTotal == 0, "DutchAuction: auction cannot have already started"); + + marketInfo.startTime = BoringMath.to64(_startTime); + marketInfo.endTime = BoringMath.to64(_endTime); + + emit AuctionTimeUpdated(_startTime,_endTime); + } + + /** + * @notice Admin can set start and min price through this function. + * @param _startPrice Auction start price. + * @param _minimumPrice Auction minimum price. + */ + function setAuctionPrice(uint256 _startPrice, uint256 _minimumPrice) external { + require(hasAdminRole(msg.sender)); + require(_startPrice > _minimumPrice, "DutchAuction: start price must be higher than minimum price"); + require(_minimumPrice > 0, "DutchAuction: minimum price must be greater than 0"); + require(marketStatus.commitmentsTotal == 0, "DutchAuction: auction cannot have already started"); + + marketPrice.startPrice = BoringMath.to128(_startPrice); + marketPrice.minimumPrice = BoringMath.to128(_minimumPrice); + + emit AuctionPriceUpdated(_startPrice,_minimumPrice); + } + + /** + * @notice Admin can set the auction wallet through this function. + * @param _wallet Auction wallet is where funds will be sent. + */ + function setAuctionWallet(address payable _wallet) external { + require(hasAdminRole(msg.sender)); + require(_wallet != address(0), "DutchAuction: wallet is the zero address"); + + wallet = _wallet; + + emit AuctionWalletUpdated(_wallet); + } + + + //-------------------------------------------------------- + // Market Launchers + //-------------------------------------------------------- + + /** + * @notice Decodes and hands auction data to the initAuction function. + * @param _data Encoded data for initialization. + */ + + function init(bytes calldata _data) external override payable { + + } + + function initMarket( + bytes calldata _data + ) public override { + ( + address _funder, + address _token, + uint256 _totalTokens, + uint256 _startTime, + uint256 _endTime, + address _paymentCurrency, + uint256 _startPrice, + uint256 _minimumPrice, + address _admin, + address _pointList, + address payable _wallet + ) = abi.decode(_data, ( + address, + address, + uint256, + uint256, + uint256, + address, + uint256, + uint256, + address, + address, + address + )); + initAuction(_funder, _token, _totalTokens, _startTime, _endTime, _paymentCurrency, _startPrice, _minimumPrice, _admin, _pointList, _wallet); + } + + /** + * @notice Collects data to initialize the auction and encodes them. + * @param _funder The address that funds the token for crowdsale. + * @param _token Address of the token being sold. + * @param _totalTokens The total number of tokens to sell in auction. + * @param _startTime Auction start time. + * @param _endTime Auction end time. + * @param _paymentCurrency The currency the crowdsale accepts for payment. Can be ETH or token address. + * @param _startPrice Starting price of the auction. + * @param _minimumPrice The minimum auction price. + * @param _admin Address that can finalize auction. + * @param _pointList Address that will manage auction approvals. + * @param _wallet Address where collected funds will be forwarded to. + * @return _data All the data in bytes format. + */ + function getAuctionInitData( + address _funder, + address _token, + uint256 _totalTokens, + uint256 _startTime, + uint256 _endTime, + address _paymentCurrency, + uint256 _startPrice, + uint256 _minimumPrice, + address _admin, + address _pointList, + address payable _wallet + ) + external + pure + returns (bytes memory _data) + { + return abi.encode( + _funder, + _token, + _totalTokens, + _startTime, + _endTime, + _paymentCurrency, + _startPrice, + _minimumPrice, + _admin, + _pointList, + _wallet + ); + } + + function getBaseInformation() external view returns( + address, + uint64, + uint64, + bool + ) { + return (auctionToken, marketInfo.startTime, marketInfo.endTime, marketStatus.finalized); + } + + function getTotalTokens() external view returns(uint256) { + return uint256(marketInfo.totalTokens); + } + +} \ No newline at end of file diff --git a/semgrep-rules/security/msg-value-multicall.yaml b/semgrep-rules/security/msg-value-multicall.yaml new file mode 100644 index 0000000..08a4a66 --- /dev/null +++ b/semgrep-rules/security/msg-value-multicall.yaml @@ -0,0 +1,35 @@ +rules: +- + id: msg-value-multicall + message: $F with constant msg.value can be called multiple times + metadata: + category: security + technology: + - solidity + cwe: "CWE-837: Improper Enforcement of a Single, Unique Action" + confidence: LOW + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://github.com/Uniswap/v3-periphery/issues/52 + - https://www.paradigm.xyz/2021/08/two-rights-might-make-a-wrong + patterns: + - pattern-either: + - pattern-inside: | + contract $C is ..., BoringBatchable, ... { + ... + } + - pattern-inside: | + contract $C is ..., Multicall, ... { + ... + } + - pattern-inside: | + function $F(...) { + ... + } + - pattern: msg.value + languages: + - solidity + severity: ERROR diff --git a/semgrep-rules/security/no-bidi-characters.sol b/semgrep-rules/security/no-bidi-characters.sol new file mode 100644 index 0000000..b99074e --- /dev/null +++ b/semgrep-rules/security/no-bidi-characters.sol @@ -0,0 +1,67 @@ +contract GuessTheNumber +{ + uint _secretNumber; + address payable _owner; + event success(string); + event wrongNumber(string); + + constructor(uint secretNumber) payable public + { + require(secretNumber <= 10); + _secretNumber = secretNumber; + _owner = msg.sender; + } + + function getValue() view public returns (uint) + { + return address(this).balance; + } + + function guess(uint n) payable public + { + require(msg.value == 1 ether); + + uint p = address(this).balance; + // ruleid: no-bidi-characters + checkAndTransferPrize(/*The prize‮/*rebmun desseug*/n , p/*‭ + /*The user who should benefit */,msg.sender); + } + +// ruleid: no-bidi-characters +// ‪ # left-to-right embedding (LRE) +// ruleid: no-bidi-characters +// ‫ # right-to-left embedding (RLE) +// ruleid: no-bidi-characters +// ‭ # left-to-right override (LRO) +// ruleid: no-bidi-characters +// ‮ # right-to-left override (RLO) +// ruleid: no-bidi-characters +//⁦ # left-to-right isolate (LRI) +// ruleid: no-bidi-characters +//⁧ # right-to-left isolate (RLI) +// ruleid: no-bidi-characters +//⁨ # first strong isolate (FSI) +// ruleid: no-bidi-characters +// ‬ # pop directional formatting (PDF) +// ruleid: no-bidi-characters +//⁩ # pop directional isolate (PDI) + + function checkAndTransferPrize(uint p, uint n, address payable guesser) internal returns(bool) + { + if(n == _secretNumber) + { + guesser.transfer(p); + emit success("You guessed the correct number!"); + } + else + { + emit wrongNumber("You've made an incorrect guess!"); + } + } + + function kill() public + { + require(msg.sender == _owner); + selfdestruct(_owner); + } +} diff --git a/semgrep-rules/security/no-bidi-characters.yaml b/semgrep-rules/security/no-bidi-characters.yaml new file mode 100644 index 0000000..e13c693 --- /dev/null +++ b/semgrep-rules/security/no-bidi-characters.yaml @@ -0,0 +1,30 @@ +rules: + - id: no-bidi-characters + message: The code must not contain any of Unicode Direction Control Characters + metadata: + category: security + technology: + - solidity + cwe: "CWE-837: Improper Enforcement of a Single, Unique Action" + confidence: HIGH + likelihood: LOW + impact: LOW + subcategory: + - audit + references: + - https://entethalliance.org/specs/ethtrust-sl/v1/#req-1-unicode-bdo + patterns: + - pattern-either: + - pattern-regex: ‪ # left-to-right embedding (LRE) + - pattern-regex: ‫ # right-to-left embedding (RLE) + - pattern-regex: ‭ # left-to-right override (LRO) + - pattern-regex: ‮ # right-to-left override (RLO) + - pattern-regex: ⁦ # left-to-right isolate (LRI) + - pattern-regex: ⁧ # right-to-left isolate (RLI) + - pattern-regex: ⁨ # first strong isolate (FSI) + - pattern-regex: ‬ # pop directional formatting (PDF) + - pattern-regex: ⁩ # pop directional isolate (PDI) + languages: + - solidity + severity: WARNING + diff --git a/semgrep-rules/security/no-slippage-check.sol b/semgrep-rules/security/no-slippage-check.sol new file mode 100644 index 0000000..9d8b0ce --- /dev/null +++ b/semgrep-rules/security/no-slippage-check.sol @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +contract UniswapSwaps { + function uniswapV2Swap( + uint amountIn, + uint amountOutMin, + uint amountOutMax + ) external returns (uint amountOut) { + weth.transferFrom(msg.sender, address(this), amountIn); + weth.approve(address(router), amountIn); + + address[] memory path; + path = new address[](2); + path[0] = WETH; + path[1] = DAI; + uint deadline = block.timestamp; + + // ok: no-slippage-check + uint[] memory amounts = router.swapExactTokensForTokens( + amountIn, + amountOutMin, + path, + msg.sender, + block.timestamp + ); + + // ruleid: no-slippage-check + uint[] memory amounts1 = router.swapExactTokensForTokens( + amountIn, + 0, + path, + msg.sender, + block.timestamp + ); + + // ok: no-slippage-check + router.swapExactETHForTokens{value: msg.value}( + amountOut, + path, + msg.sender, + deadline + ); + + // ruleid: no-slippage-check + router.swapExactETHForTokens{value: msg.value}( + 0, + path, + msg.sender, + deadline + ); + + // ok: no-slippage-check + router.swapTokensForExactTokens( + amountOutDesired, + amountOutMax, + path, + msg.sender, + block.timestamp + ); + + // ok: no-slippage-check + router.swapTokensForExactTokens( + amounts[i], + (inputAmount[0] * (BASE_UNIT + slippage)) / BASE_UNIT, + elkPaths[address(tokens[i])], + msg.sender, + block.timestamp + 1000 + ); + + // ruleid: no-slippage-check + router.swapTokensForExactTokens( + amounts[i], + 2 ** 256 - 1, + elkPaths[address(tokens[i])], + msg.sender, + block.timestamp + 1000 + ); + + // ruleid: no-slippage-check + router.swapTokensForExactTokens( + amountOutDesired, + uint256(-1), + path, + msg.sender, + block.timestamp + ); + + // ok: no-slippage-check + router.swapTokensForExactETH( + amountOut, + amountInMax, + path, + msg.sender, + deadline + ); + + // ruleid: no-slippage-check + router.swapTokensForExactETH( + amountOut, + type(uint256).max, + path, + msg.sender, + deadline + ); + + // ok: no-slippage-check + router.swapExactTokensForETH(amountIn, amountOutMin, path, msg.sender, deadline); + + // ruleid: no-slippage-check + router.swapExactTokensForETH(amountIn, 0, path, msg.sender, deadline); + + // ok: no-slippage-check + router.swapExactTokensForTokensSupportingFeeOnTransferTokens(amountIn, amountOutMin, path, msg.sender, deadline); + + // ruleid: no-slippage-check + router.swapExactTokensForTokensSupportingFeeOnTransferTokens(amountIn, 0, path, msg.sender, deadline); + + // ok: no-slippage-check + router.swapExactETHForTokensSupportingFeeOnTransferTokens{value: 123}(amountOutMin, path, msg.sender, deadline); + + // ruleid: no-slippage-check + router.swapExactETHForTokensSupportingFeeOnTransferTokens{value: msg.value}(0, path, msg.sender, deadline); + + // ok: no-slippage-check + router.swapExactTokensForETHSupportingFeeOnTransferTokens(amountIn, amountOutMin, path, msg.sender, deadline); + + // ruleid: no-slippage-check + router.swapExactTokensForETHSupportingFeeOnTransferTokens(amountIn, 0, path, msg.sender, deadline); + + // ok: no-slippage-check + pool.swap( + address(this), + swapQuantity > 0, + swapQuantity > 0 ? swapQuantity : -swapQuantity, + sqrtPriceLimitX96, + abi.encode(amountMin) + ); + + // ok: no-slippage-check + pair.swap(amount0Out, amount1Out, to, new bytes(0)); + // ok: no-slippage-check + pair.swap(0, amountOut, to, new bytes(0)); + // ok: no-slippage-check + pair.swap(amountOut, 0, to, new bytes(0)); + + return amounts[1]; + } + + // ruleid: no-slippage-check + function uniswapV3Swap1( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external override { + FlashCallbackData memory decoded = abi.decode(data, (FlashCallbackData)); + CallbackValidation.verifyCallback(factory, decoded.poolKey); + + address token0 = decoded.poolKey.token0; + address token1 = decoded.poolKey.token1; + + uint256 amount0Min = LowGasSafeMath.add(decoded.amount0, fee0); + uint256 amount1Min = LowGasSafeMath.add(decoded.amount1, fee1); + + TransferHelper.safeApprove(token1, address(swapRouter), decoded.amount1); + ISwapRouter.ExactInputSingleParams params = ISwapRouter.ExactInputSingleParams({ + tokenIn: token1, + tokenOut: token0, + fee: decoded.poolFee2, + recipient: address(this), + deadline: block.timestamp, + amountIn: decoded.amount1, + amountOutMinimum: 0, + sqrtPriceLimitX96: 0 + }) + uint256 amountOut0 = + swapRouter.exactInputSingle( + params + ); + } + + // ok: no-slippage-check + function uniswapV3Swap1Ok( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external override { + FlashCallbackData memory decoded = abi.decode(data, (FlashCallbackData)); + CallbackValidation.verifyCallback(factory, decoded.poolKey); + + address token0 = decoded.poolKey.token0; + address token1 = decoded.poolKey.token1; + + uint256 amount0Min = LowGasSafeMath.add(decoded.amount0, fee0); + uint256 amount1Min = LowGasSafeMath.add(decoded.amount1, fee1); + + TransferHelper.safeApprove(token1, address(swapRouter), decoded.amount1); + ISwapRouter.ExactInputSingleParams params = ISwapRouter.ExactInputSingleParams({ + tokenIn: token1, + tokenOut: token0, + fee: decoded.poolFee2, + recipient: address(this), + deadline: block.timestamp, + amountIn: decoded.amount1, + amountOutMinimum: amount0Min, + sqrtPriceLimitX96: 0 + }) + uint256 amountOut0 = + swapRouter.exactInputSingle( + params + ); + } + + function uniswapV3Swap2( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external override { + FlashCallbackData memory decoded = abi.decode(data, (FlashCallbackData)); + CallbackValidation.verifyCallback(factory, decoded.poolKey); + + address token0 = decoded.poolKey.token0; + address token1 = decoded.poolKey.token1; + + uint256 amount0Min = LowGasSafeMath.add(decoded.amount0, fee0); + uint256 amount1Min = LowGasSafeMath.add(decoded.amount1, fee1); + + TransferHelper.safeApprove(token1, address(swapRouter), decoded.amount1); + // ruleid: no-slippage-check + uint256 amountOut0 = + swapRouter.exactInputSingle( + ISwapRouter.ExactInputSingleParams({ + tokenIn: token1, + tokenOut: token0, + fee: decoded.poolFee2, + recipient: address(this), + deadline: block.timestamp, + amountIn: decoded.amount1, + amountOutMinimum: 0, + sqrtPriceLimitX96: 0 + }) + ); + } + + // ok: no-slippage-check + function uniswapV3Swap2Ok( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external override { + FlashCallbackData memory decoded = abi.decode(data, (FlashCallbackData)); + CallbackValidation.verifyCallback(factory, decoded.poolKey); + + address token0 = decoded.poolKey.token0; + address token1 = decoded.poolKey.token1; + + uint256 amount0Min = LowGasSafeMath.add(decoded.amount0, fee0); + uint256 amount1Min = LowGasSafeMath.add(decoded.amount1, fee1); + + TransferHelper.safeApprove(token1, address(swapRouter), decoded.amount1); + uint256 amountOut0 = + swapRouter.exactInputSingle( + ISwapRouter.ExactInputSingleParams({ + tokenIn: token1, + tokenOut: token0, + fee: decoded.poolFee2, + recipient: address(this), + deadline: block.timestamp, + amountIn: decoded.amount1, + amountOutMinimum: amount0Min, + sqrtPriceLimitX96: 0 + }) + ); + } + + // ok: no-slippage-check + function uniswapV3Swap3Ok(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) { + TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum); + TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum); + + ISwapRouter.ExactOutputSingleParams memory params = + ISwapRouter.ExactOutputSingleParams({ + tokenIn: DAI, + tokenOut: WETH9, + fee: poolFee, + recipient: msg.sender, + deadline: block.timestamp, + amountOut: amountOut, + amountInMaximum: amountInMaximum, + sqrtPriceLimitX96: 0 + }); + + amountIn = swapRouter.exactOutputSingle(params); + + if (amountIn < amountInMaximum) { + TransferHelper.safeApprove(DAI, address(swapRouter), 0); + TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn); + } + } + + // ruleid: no-slippage-check + function uniswapV3Swap3Ok(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) { + TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum); + TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum); + + ISwapRouter.ExactOutputSingleParams memory params = + ISwapRouter.ExactOutputSingleParams({ + tokenIn: DAI, + tokenOut: WETH9, + fee: poolFee, + recipient: msg.sender, + deadline: block.timestamp, + amountOut: amountOut, + amountInMaximum: uint(-1), + sqrtPriceLimitX96: 0 + }); + + amountIn = swapRouter.exactOutputSingle(params); + + if (amountIn < amountInMaximum) { + TransferHelper.safeApprove(DAI, address(swapRouter), 0); + TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn); + } + } + + + function uniswapV3Swap3Ok(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) { + TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum); + TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum); + // ok: no-slippage-check + amountIn = swapRouter.exactOutputSingle(ISwapRouter.ExactOutputSingleParams({ + tokenIn: DAI, + tokenOut: WETH9, + fee: poolFee, + recipient: msg.sender, + deadline: block.timestamp, + amountOut: amountOut, + amountInMaximum: amountInMaximum, + sqrtPriceLimitX96: 0 + })); + + if (amountIn < amountInMaximum) { + TransferHelper.safeApprove(DAI, address(swapRouter), 0); + TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn); + } + } + + + function uniswapV3Swap3Ok(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) { + TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum); + TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum); + // ruleid: no-slippage-check + amountIn = swapRouter.exactOutputSingle(ISwapRouter.ExactOutputSingleParams({ + tokenIn: DAI, + tokenOut: WETH9, + fee: poolFee, + recipient: msg.sender, + deadline: block.timestamp, + amountOut: amountOut, + amountInMaximum: uint256(-1), + sqrtPriceLimitX96: 0 + })); + + if (amountIn < amountInMaximum) { + TransferHelper.safeApprove(DAI, address(swapRouter), 0); + TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn); + } + } + + function exactInputInternalOk( + uint256 amountIn, + address recipient, + uint160 sqrtPriceLimitX96, + SwapCallbackData memory data + ) external returns (uint256 amountOut) { + // allow swapping to the router address with address 0 + if (recipient == address(0)) recipient = address(this); + + (address tokenIn, address tokenOut, uint24 fee) = data.path.decodeFirstPool(); + + bool zeroForOne = tokenIn < tokenOut; + + (int256 amount0, int256 amount1) = + // ok: no-slippage-check + getPool(tokenIn, tokenOut, fee).swap( + recipient, + zeroForOne, + amountIn.toInt256(), + sqrtPriceLimitX96, + abi.encode(data) + ); + + return uint256(-(zeroForOne ? amount1 : amount0)); + } + + function exactInputInternalOk( + uint256 amountIn, + address recipient, + uint160 sqrtPriceLimitX96, + SwapCallbackData memory data + ) external returns (uint256 amountOut) { + // allow swapping to the router address with address 0 + if (recipient == address(0)) recipient = address(this); + + (address tokenIn, address tokenOut, uint24 fee) = data.path.decodeFirstPool(); + + bool zeroForOne = tokenIn < tokenOut; + + (int256 amount0, int256 amount1) = + // ruleid: no-slippage-check + getPool(tokenIn, tokenOut, fee).swap( + recipient, + zeroForOne, + amountIn.toInt256(), + 0, + abi.encode(data) + ); + + return uint256(-(zeroForOne ? amount1 : amount0)); + } +} diff --git a/semgrep-rules/security/no-slippage-check.yaml b/semgrep-rules/security/no-slippage-check.yaml new file mode 100644 index 0000000..4ef303b --- /dev/null +++ b/semgrep-rules/security/no-slippage-check.yaml @@ -0,0 +1,89 @@ +rules: + - id: no-slippage-check + message: No slippage check in a Uniswap v2/v3 trade + metadata: + category: security + technology: + - solidity + cwe: "CWE-682: Incorrect Calculation" + confidence: MEDIUM + likelihood: HIGH + impact: MEDIUM + subcategory: + - vuln + references: + - https://uniswapv3book.com/docs/milestone_3/slippage-protection/ + patterns: + - pattern-either: + - pattern: $X.swapExactTokensForTokens($A, $LIMIT, $B, $C, $D) + - pattern: $X.swapExactTokensForTokensSupportingFeeOnTransferTokens($A, $LIMIT, $B, $C, $D) + - pattern: $X.swapExactTokensForETH($A, $LIMIT, $B, $C, $D) + - pattern: $X.swapExactTokensForETHSupportingFeeOnTransferTokens($A, $LIMIT, $B, $C, $D) + - pattern: $X.swapExactETHForTokens{$VALUE:...}($LIMIT, $A, $B, $C) + - pattern: $X.swapExactETHForTokensSupportingFeeOnTransferTokens{$VALUE:...}($LIMIT, $A, $B, $C) + - pattern: $X.swapTokensForExactTokens($A, $LIMIT, $B, $C, $D) + - pattern: $X.swapTokensForExactETH($A, $LIMIT, $B, $C, $D) + - pattern: > + function $FUNC(...) { + ... + $Y = $SWAPROUTER.ExactInputSingleParams({ + tokenIn: $A, + tokenOut: $B, + fee: $C, + recipient: $D, + deadline: $E, + amountIn: $F, + amountOutMinimum: $LIMIT, + sqrtPriceLimitX96: 0 + }); + ... + $X.exactInputSingle($Y); + ... + } + - pattern: > + $X.exactInputSingle($SWAPROUTER.ExactInputSingleParams({ + tokenIn: $A, + tokenOut: $B, + fee: $C, + recipient: $D, + deadline: $E, + amountIn: $F, + amountOutMinimum: $LIMIT, + sqrtPriceLimitX96: 0 + })); + - pattern: > + function $FUNC(...) { + ... + $Y = $SWAPROUTER.ExactOutputSingleParams({ + tokenIn: $A, + tokenOut: $B, + fee: $C, + recipient: $D, + deadline: $E, + amountOut: $F, + amountInMaximum: $LIMIT, + sqrtPriceLimitX96: 0 + }); + ... + $X.exactOutputSingle($Y); + ... + } + - pattern: > + $X.exactOutputSingle($SWAPROUTER.ExactOutputSingleParams({ + tokenIn: $A, + tokenOut: $B, + fee: $C, + recipient: $D, + deadline: $E, + amountOut: $F, + amountInMaximum: $LIMIT, + sqrtPriceLimitX96: 0 + })); + - pattern: $X.swap($RECIPIENT, $ZEROFORONE, $AMOUNTIN, $LIMIT, $DATA) + - metavariable-regex: + metavariable: $LIMIT + regex: ^(0)|(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)|(type\(uint(256)?\)\.max)|(uint(256)?\(-1)|(115792089237316195423570985008687907853269984665640564039457584007913129639935)|(2\s?\*\*\s?256\s?-\s?1)$ + languages: + - solidity + severity: ERROR + diff --git a/semgrep-rules/security/olympus-dao-staking-incorrect-call-order.sol b/semgrep-rules/security/olympus-dao-staking-incorrect-call-order.sol new file mode 100644 index 0000000..6f1808b --- /dev/null +++ b/semgrep-rules/security/olympus-dao-staking-incorrect-call-order.sol @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.7.5; + +import "./libraries/SafeMath.sol"; +import "./libraries/SafeERC20.sol"; + +import "./interfaces/IERC20.sol"; +import "./interfaces/IsFLOOR.sol"; +import "./interfaces/IgFLOOR.sol"; +import "./interfaces/IDistributor.sol"; + +import "./types/FloorAccessControlled.sol"; + +contract FloorStaking is FloorAccessControlled { + /* ========== DEPENDENCIES ========== */ + + using SafeMath for uint256; + using SafeERC20 for IERC20; + using SafeERC20 for IsFLOOR; + using SafeERC20 for IgFLOOR; + + /* ========== EVENTS ========== */ + + event DistributorSet(address distributor); + event WarmupSet(uint256 warmup); + + /* ========== DATA STRUCTURES ========== */ + + struct Epoch { + uint256 length; // in seconds + uint256 number; // since inception + uint256 end; // timestamp + uint256 distribute; // amount + } + + struct Claim { + uint256 deposit; // if forfeiting + uint256 gons; // staked balance + uint256 expiry; // end of warmup period + bool lock; // prevents malicious delays for claim + } + + /* ========== STATE VARIABLES ========== */ + + IERC20 public immutable FLOOR; + IsFLOOR public immutable sFLOOR; + IgFLOOR public immutable gFLOOR; + + Epoch public epoch; + + IDistributor public distributor; + + mapping(address => Claim) public warmupInfo; + uint256 public warmupPeriod; + uint256 private gonsInWarmup; + + /* ========== CONSTRUCTOR ========== */ + + constructor( + address _floor, + address _sFLOOR, + address _gFLOOR, + uint256 _epochLength, + uint256 _firstEpochNumber, + uint256 _firstEpochTime, + address _authority + ) FloorAccessControlled(IFloorAuthority(_authority)) { + require(_floor != address(0), "Zero address: FLOOR"); + FLOOR = IERC20(_floor); + require(_sFLOOR != address(0), "Zero address: sFLOOR"); + sFLOOR = IsFLOOR(_sFLOOR); + require(_gFLOOR != address(0), "Zero address: gFLOOR"); + gFLOOR = IgFLOOR(_gFLOOR); + + epoch = Epoch({length: _epochLength, number: _firstEpochNumber, end: _firstEpochTime, distribute: 0}); + } + + /* ========== MUTATIVE FUNCTIONS ========== */ + + /** + * @notice stake FLOOR to enter warmup + * @param _to address + * @param _amount uint + * @param _claim bool + * @param _rebasing bool + * @return uint + */ + function stake( + address _to, + uint256 _amount, + bool _rebasing, + bool _claim + ) external returns (uint256) { + //ruleid: olympus-dao-staking-incorrect-call-order + FLOOR.safeTransferFrom(msg.sender, address(this), _amount); + _amount = _amount.add(rebase()); // add bounty if rebase occurred + if (_claim && warmupPeriod == 0) { + return _send(_to, _amount, _rebasing); + } else { + Claim memory info = warmupInfo[_to]; + if (!info.lock) { + require(_to == msg.sender, "External deposits for account are locked"); + } + + warmupInfo[_to] = Claim({ + deposit: info.deposit.add(_amount), + gons: info.gons.add(sFLOOR.gonsForBalance(_amount)), + expiry: epoch.number.add(warmupPeriod), + lock: info.lock + }); + + gonsInWarmup = gonsInWarmup.add(sFLOOR.gonsForBalance(_amount)); + + return _amount; + } + } + + + /** + * @notice trigger rebase if epoch over + * @return uint256 + */ + function rebase() public returns (uint256) { + uint256 bounty; + if (epoch.end <= block.timestamp) { + sFLOOR.rebase(epoch.distribute, epoch.number); + + epoch.end = epoch.end.add(epoch.length); + epoch.number++; + + if (address(distributor) != address(0)) { + distributor.distribute(); + bounty = distributor.retrieveBounty(); // Will mint floor for this contract if there exists a bounty + } + uint256 balance = FLOOR.balanceOf(address(this)); + uint256 staked = sFLOOR.circulatingSupply(); + if (balance <= staked.add(bounty)) { + epoch.distribute = 0; + } else { + epoch.distribute = balance.sub(staked).sub(bounty); + } + } + return bounty; + } + +} + + +contract QWAStaking is Ownable { + + /// DATA STRUCTURES /// + + struct Epoch { + uint256 length; // in seconds + uint256 number; // since inception + uint256 end; // timestamp + uint256 distribute; // amount + } + + /// STATE VARIABLES /// + + /// @notice QWA address + IERC20 public immutable QWA; + /// @notice sQWA address + IsQWA public immutable sQWA; + + /// @notice Current epoch details + Epoch public epoch; + + /// @notice Distributor address + IDistributor public distributor; + + /// CONSTRUCTOR /// + + /// @param _QWA Address of QWA + /// @param _sQWA Address of sQWA + /// @param _epochLength Epoch length + /// @param _secondsTillFirstEpoch Seconds till first epoch starts + constructor( + address _QWA, + address _sQWA, + uint256 _epochLength, + uint256 _secondsTillFirstEpoch + ) { + QWA = IERC20(_QWA); + sQWA = IsQWA(_sQWA); + + epoch = Epoch({ + length: _epochLength, + number: 0, + end: block.timestamp + _secondsTillFirstEpoch, + distribute: 0 + }); + } + + /// MUTATIVE FUNCTIONS /// + + /// @notice stake QWA + /// @param _to address + /// @param _amount uint + function stake(address _to, uint256 _amount) external { + //ok: olympus-dao-staking-incorrect-call-order + rebase(); + QWA.transferFrom(msg.sender, address(this), _amount); + sQWA.transfer(_to, _amount); + } + + /// @notice redeem sQWA for QWA + /// @param _to address + /// @param _amount uint + function unstake(address _to, uint256 _amount, bool _rebase) external { + if (_rebase) rebase(); + sQWA.transferFrom(msg.sender, address(this), _amount); + require( + _amount <= QWA.balanceOf(address(this)), + "Insufficient QWA balance in contract" + ); + QWA.transfer(_to, _amount); + } + + ///@notice Trigger rebase if epoch over + function rebase() public { + if (epoch.end <= block.timestamp) { + sQWA.rebase(epoch.distribute, epoch.number); + + epoch.end = epoch.end + epoch.length; + epoch.number++; + + if (address(distributor) != address(0)) { + distributor.distribute(); + } + + uint256 balance = QWA.balanceOf(address(this)); + uint256 staked = sQWA.circulatingSupply(); + + if (balance <= staked) { + epoch.distribute = 0; + } else { + epoch.distribute = balance - staked; + } + } + } +} + +contract HATEStaking is Ownable{ + /* ========== EVENTS ========== */ + + event DistributorSet(address distributor); + + /* ========== DATA STRUCTURES ========== */ + + struct Epoch { + uint256 length; // in seconds + uint256 number; // since inception + uint256 end; // timestamp + uint256 distribute; // amount + } + + struct Claim { + uint256 deposit; // if forfeiting + uint256 gons; // staked balance + uint256 expiry; // end of warmup period + bool lock; // prevents malicious delays for claim + } + + /* ========== STATE VARIABLES ========== */ + + IERC20 public immutable HATE; + IsHATE public immutable sHATE; + + Epoch public epoch; + + IDistributor public distributor; + + /* ========== CONSTRUCTOR ========== */ + + constructor(address _HATE, address _sHATE, uint256 _epochLength) { + require(_HATE != address(0), "Zero address: HATE"); + HATE = IERC20(_HATE); + require(_sHATE != address(0), "Zero address: sHATE"); + sHATE = IsHATE(_sHATE); + + epoch = Epoch({length: _epochLength, number: 0, end: block.timestamp + _epochLength, distribute: 0}); + } + + /* ========== MUTATIVE FUNCTIONS ========== */ + + /** + * @notice stake HATE + * @param _to address + * @param _amount uint + */ + function stake(address _to, uint256 _amount) external { + //ruleid: olympus-dao-staking-incorrect-call-order + HATE.transferFrom(msg.sender, address(this), _amount); + rebase(); + sHATE.transfer(_to, _amount); + } + + /** + * @notice redeem sHATE for HATEs + * @param _to address + * @param _amount uint + */ + function unstake(address _to, uint256 _amount, bool _rebase) external { + if (_rebase) rebase(); + sHATE.transferFrom(msg.sender, address(this), _amount); + require(_amount <= HATE.balanceOf(address(this)), "Insufficient HATE balance in contract"); + HATE.transfer(_to, _amount); + } + + /** + * @notice trigger rebase if epoch over + */ + function rebase() public { + if (epoch.end <= block.timestamp) { + sHATE.rebase(epoch.distribute, epoch.number); + + epoch.end = epoch.end + epoch.length; + epoch.number++; + + if (address(distributor) != address(0)) { + distributor.distribute(); + } + + uint256 balance = HATE.balanceOf(address(this)); + uint256 staked = sHATE.circulatingSupply(); + + if (balance <= staked) { + epoch.distribute = 0; + } else { + epoch.distribute = balance - staked; + } + } + } +} + +contract Staking is Ownable { + /// EVENTS /// + + event DistributorSet(address distributor); + + /// DATA STRUCTURES /// + + struct Epoch { + uint256 length; // in seconds + uint256 number; // since inception + uint256 end; // timestamp + uint256 distribute; // amount + } + + /// STATE VARIABLES /// + + /// @notice TOKEN address + IERC20 public immutable TOKEN; + /// @notice sTOKEN address + IsStakingProtocol public immutable sTOKEN; + + /// @notice Current epoch details + Epoch public epoch; + + /// @notice Distributor address + IDistributor public distributor; + + /// CONSTRUCTOR /// + + /// @param _TOKEN Address of TOKEN + /// @param _sTOKEN Address of sTOKEN + /// @param _epochLength Epoch length + /// @param _secondsTillFirstEpoch Seconds till first epoch starts + constructor( + address _TOKEN, + address _sTOKEN, + uint256 _epochLength, + uint256 _secondsTillFirstEpoch + ) { + require(_TOKEN != address(0), "Zero address: TOKEN"); + TOKEN = IERC20(_TOKEN); + require(_sTOKEN != address(0), "Zero address"); + sTOKEN = IsStakingProtocol(_sTOKEN); + + epoch = Epoch({ + length: _epochLength, + number: 0, + end: block.timestamp + _secondsTillFirstEpoch, + distribute: 0 + }); + } + + /// MUTATIVE FUNCTIONS /// + + /// @notice stake TOKEN + /// @param _to address + /// @param _amount uint + function stake(address _to, uint256 _amount) external { + //ok: olympus-dao-staking-incorrect-call-order + rebase(); + TOKEN.transferFrom(msg.sender, address(this), _amount); + sTOKEN.transfer(_to, _amount); + } + + /// @notice redeem sTOKEN for TOKEN + /// @param _to address + /// @param _amount uint + function unstake(address _to, uint256 _amount, bool _rebase) external { + if (_rebase) rebase(); + sTOKEN.transferFrom(msg.sender, address(this), _amount); + require( + _amount <= TOKEN.balanceOf(address(this)), + "Insufficient TOKEN balance in contract" + ); + TOKEN.transfer(_to, _amount); + } + + ///@notice Trigger rebase if epoch over + function rebase() public { + if (epoch.end <= block.timestamp) { + sTOKEN.rebase(epoch.distribute, epoch.number); + + epoch.end = epoch.end + epoch.length; + epoch.number++; + + if (address(distributor) != address(0)) { + distributor.distribute(); + } + + uint256 balance = TOKEN.balanceOf(address(this)); + uint256 staked = sTOKEN.circulatingSupply(); + + if (balance <= staked) { + epoch.distribute = 0; + } else { + epoch.distribute = balance - staked; + } + } + } +} \ No newline at end of file diff --git a/semgrep-rules/security/olympus-dao-staking-incorrect-call-order.yaml b/semgrep-rules/security/olympus-dao-staking-incorrect-call-order.yaml new file mode 100644 index 0000000..ae99cfc --- /dev/null +++ b/semgrep-rules/security/olympus-dao-staking-incorrect-call-order.yaml @@ -0,0 +1,34 @@ +rules: + - + id: olympus-dao-staking-incorrect-call-order + message: The order of calling the transferFrom() and rebase() functions is incorrect in Olympus DAO forks + metadata: + category: security + technology: + - solidity + cwe: "CWE-841: Improper Enforcement of Behavioral Workflow" + confidence: MEDIUM + likelihood: LOW + impact: HIGH + subcategory: + - vuln + references: + - https://medium.com/floordao/floor-post-mortem-incident-summary-september-5-2023-e054a2d5afa4 + - https://github.com/OlympusDAO/olympus-contracts/issues/172 + - https://twitter.com/DecurityHQ/status/1699384904218202618 + patterns: + - pattern-inside: | + function stake(...) { + ... + } + - pattern: | + $TOKEN.$TRANSFERFROM(...); + ... + rebase(...); + - metavariable-regex: + metavariable: $TRANSFERFROM + regex: (transferFrom|safeTransferFrom) + + languages: + - solidity + severity: WARNING \ No newline at end of file diff --git a/semgrep-rules/security/openzeppelin-ecdsa-recover-malleable.sol b/semgrep-rules/security/openzeppelin-ecdsa-recover-malleable.sol new file mode 100644 index 0000000..da5893d --- /dev/null +++ b/semgrep-rules/security/openzeppelin-ecdsa-recover-malleable.sol @@ -0,0 +1,20 @@ +contract Kek { + mapping (bytes => bool) lulz; + // ruleid: openzeppelin-ecdsa-recover-malleable + function lol(bytes digest, bytes memory signature) { + uint a = 1; + (address recoveredAddress, ECDSA.RecoverError err) = ECDSA.recover(digest, signature); + string b = 2; + lulz[signature] = true; + bool c = 3; + } + + // ruleid: openzeppelin-ecdsa-recover-malleable + function lol2(bytes memory signature, bytes digest) { + uint a = 1; + (address recoveredAddress, ECDSA.RecoverError err) = ECDSA.recover(digest, signature); + string b = 2; + lulz[signature] = true; + bool c = 3; + } +} diff --git a/semgrep-rules/security/openzeppelin-ecdsa-recover-malleable.yaml b/semgrep-rules/security/openzeppelin-ecdsa-recover-malleable.yaml new file mode 100644 index 0000000..952718b --- /dev/null +++ b/semgrep-rules/security/openzeppelin-ecdsa-recover-malleable.yaml @@ -0,0 +1,36 @@ +rules: + - + id: openzeppelin-ecdsa-recover-malleable + message: Potential signature malleability in $F + metadata: + category: security + technology: + - solidity + cwe: "CWE-347: Improper Verification of Cryptographic Signature" + confidence: LOW + likelihood: MEDIUM + impact: MEDIUM + subcategory: + - vuln + references: + - https://github.com/advisories/GHSA-4h98-2769-gh6h + pattern-either: + - pattern: | + function $F(..., bytes $Y, ...) { + ... + $Z = ECDSA.recover(..., $Y); + ... + $A[$Y] = ...; + ... + } + - pattern: | + function $F(..., bytes $Y, ...) { + ... + $Z = ECDSA.recover(..., $Y); + ... + $A[$B][$Y] = ...; + ... + } + languages: + - solidity + severity: WARNING diff --git a/semgrep-rules/security/oracle-price-update-not-restricted.sol b/semgrep-rules/security/oracle-price-update-not-restricted.sol new file mode 100644 index 0000000..ea13a1a --- /dev/null +++ b/semgrep-rules/security/oracle-price-update-not-restricted.sol @@ -0,0 +1,148 @@ +pragma solidity ^0.5.16; + +import "./PriceOracle.sol"; +import "./RBep20.sol"; + +interface oracleChainlink { + function decimals() external view returns (uint8); + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); +} + +contract SimplePriceOracle is PriceOracle { + mapping(address => uint) prices; + + event PricePosted(address asset, uint previousPriceMantissa, uint requestedPriceMantissa, uint newPriceMantissa); + + mapping(address => oracleChainlink) public oracleData; + + constructor() public { + } + // ruleid: oracle-price-update-not-restricted + function setOracleData(address rToken, oracleChainlink _oracle) external { + oracleData[rToken] = _oracle; + } + + function getUnderlyingPrice(RToken rToken) public view returns (uint) { + uint decimals = oracleData[address(rToken)].decimals(); + (uint80 roundId,int256 answer,uint256 startedAt,uint256 updatedAt,uint80 answeredInRound) = oracleData[address(rToken)].latestRoundData(); + return 10 ** (18 - decimals) * uint(answer); + } +} + + +// AAVE v2 (but vulnerable) +contract AaveFallbackOracle is Ownable, IPriceOracleGetter { + using SafeMath for uint256; + struct Price { + uint64 blockNumber; + uint64 blockTimestamp; + uint128 price; + } + event PricesSubmitted(address sybil, address[] assets, uint128[] prices); + event SybilAuthorized(address indexed sybil); + event SybilUnauthorized(address indexed sybil); + uint256 public constant PERCENTAGE_BASE = 1e4; + mapping(address => Price) private _prices; + mapping(address => bool) private _sybils; + modifier onlySybil { + _requireWhitelistedSybil(msg.sender); + _; + } + function authorizeSybil(address sybil) external onlyOwner { + _sybils[sybil] = true; + emit SybilAuthorized(sybil); + } + function unauthorizeSybil(address sybil) external onlyOwner { + _sybils[sybil] = false; + emit SybilUnauthorized(sybil); + } + // ok: oracle-price-update-not-restricted + function submitPrices(address[] calldata assets, uint128[] calldata prices) external onlySybil { + require(assets.length == prices.length, 'INCONSISTENT_PARAMS_LENGTH'); + for (uint256 i = 0; i < assets.length; i++) { + _prices[assets[i]] = Price(uint64(block.number), uint64(block.timestamp), prices[i]); + } + emit PricesSubmitted(msg.sender, assets, prices); + } + function getAssetPrice(address asset) external view override returns (uint256) { + return uint256(_prices[asset].price); + } + function isSybilWhitelisted(address sybil) public view returns (bool) { + return _sybils[sybil]; + } + function getPricesData(address[] calldata assets) external view returns (Price[] memory) { + Price[] memory result = new Price[](assets.length); + for (uint256 i = 0; i < assets.length; i++) { + result[i] = _prices[assets[i]]; + } + return result; + } + function filterCandidatePricesByDeviation( + uint256 deviation, + address[] calldata assets, + uint256[] calldata candidatePrices + ) external view returns (address[] memory, uint256[] memory) { + require(assets.length == candidatePrices.length, 'INCONSISTENT_PARAMS_LENGTH'); + address[] memory filteredAssetsWith0s = new address[](assets.length); + uint256[] memory filteredCandidatesWith0s = new uint256[](assets.length); + uint256 end0sInLists; + for (uint256 i = 0; i < assets.length; i++) { + uint128 currentOraclePrice = _prices[assets[i]].price; + if ( + uint256(currentOraclePrice) > + candidatePrices[i].mul(PERCENTAGE_BASE.add(deviation)).div(PERCENTAGE_BASE) || + uint256(currentOraclePrice) < + candidatePrices[i].mul(PERCENTAGE_BASE.sub(deviation)).div(PERCENTAGE_BASE) + ) { + filteredAssetsWith0s[end0sInLists] = assets[i]; + filteredCandidatesWith0s[end0sInLists] = candidatePrices[i]; + end0sInLists++; + } + } + address[] memory resultAssets = new address[](end0sInLists); + uint256[] memory resultPrices = new uint256[](end0sInLists); + for (uint256 i = 0; i < end0sInLists; i++) { + resultAssets[i] = filteredAssetsWith0s[i]; + resultPrices[i] = filteredCandidatesWith0s[i]; + } + return (resultAssets, resultPrices); + } + function _requireWhitelistedSybil(address sybil) internal view { + require(isSybilWhitelisted(sybil), 'INVALID_SYBIL'); + } +} + +// AAVE v3 (vulnerable) + +import {IPriceOracle} from '../../interfaces/IPriceOracle.sol'; +contract PriceOracle is IPriceOracle { + // Map of asset prices (asset => price) + mapping(address => uint256) internal prices; + uint256 internal ethPriceUsd; + event AssetPriceUpdated(address asset, uint256 price, uint256 timestamp); + event EthPriceUpdated(uint256 price, uint256 timestamp); + function getAssetPrice(address asset) external view override returns (uint256) { + return prices[asset]; + } + // ruleid: oracle-price-update-not-restricted + function setAssetPrice(address asset, uint256 price) external override { + prices[asset] = price; + emit AssetPriceUpdated(asset, price, block.timestamp); + } + function getEthUsdPrice() external view returns (uint256) { + return ethPriceUsd; + } + function setEthUsdPrice(uint256 price) external { + ethPriceUsd = price; + emit EthPriceUpdated(price, block.timestamp); + } +} \ No newline at end of file diff --git a/semgrep-rules/security/oracle-price-update-not-restricted.yaml b/semgrep-rules/security/oracle-price-update-not-restricted.yaml new file mode 100644 index 0000000..7f80aa7 --- /dev/null +++ b/semgrep-rules/security/oracle-price-update-not-restricted.yaml @@ -0,0 +1,35 @@ +rules: + - + id: oracle-price-update-not-restricted + message: Oracle price data can be submitted by anyone + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: LOW + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/BlockSecTeam/status/1514815673800663045 + - https://twitter.com/CertiKAlert/status/1514831117169405953 + - https://medium.com/@hacxyk/aave-v3s-price-oracle-manipulation-vulnerability-168e44e9e374 + - https://bscscan.com/address/0xd55f01b4b51b7f48912cd8ca3cdd8070a1a9dba5 # Rikkei + - https://polygonscan.com/address/0xaA5890362f36FeaAe91aF248e84e287cE6eCD1A9 # AAVE + patterns: + - pattern-either: + - pattern: function $F(...) public {...} + - pattern: function $F(...) external {...} + - metavariable-pattern: + metavariable: $F + pattern-either: + - pattern: setOracleData + - pattern: setAssetPrice + - pattern-not: function $F(...) onlyOwner { ... } + - pattern-not: function $F(...) onlySybil { ... } + languages: + - solidity + severity: ERROR + diff --git a/semgrep-rules/security/oracle-uses-curve-spot-price.sol b/semgrep-rules/security/oracle-uses-curve-spot-price.sol new file mode 100644 index 0000000..2565589 --- /dev/null +++ b/semgrep-rules/security/oracle-uses-curve-spot-price.sol @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.6.6; + +import {FullMath} from '../uniswap-v2/libraries/FullMath.sol'; +import {IPriceGetter} from '../uniswap-v2/interfaces/IPriceGetter.sol'; +import {IERC20Metadata} from '../uniswap-v2/interfaces/IERC20Metadata.sol'; + +interface ICurvePoolWithOracle { + function price_oracle(uint256 i) external view returns (uint256); + + function get_p(uint256 i) external view returns (uint256); +} + +interface IUwUOracle { + function getAssetPrice(address asset) external view returns (uint256); +} + +contract sUSDePriceProviderBUniCatch is IPriceGetter { + address public constant FRAX = 0x853d955aCEf822Db058eb8505911ED77F175b99e; + address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address public constant CRVUSD = 0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E; + address public constant GHO = 0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f; + ICurvePoolWithOracle public constant FRAX_POOL = + ICurvePoolWithOracle(0x5dc1BF6f1e983C0b21EfB003c105133736fA0743); + ICurvePoolWithOracle public constant DAI_POOL = + ICurvePoolWithOracle(0xF36a4BA50C603204c3FC6d2dA8b78A7b69CBC67d); + ICurvePoolWithOracle public constant USDC_POOL = + ICurvePoolWithOracle(0x02950460E2b9529D0E00284A5fA2d7bDF3fA4d72); + ICurvePoolWithOracle public constant CRVUSD_POOL = + ICurvePoolWithOracle(0xF55B0f6F2Da5ffDDb104b58a60F2862745960442); + ICurvePoolWithOracle public constant GHO_POOL = + ICurvePoolWithOracle(0x670a72e6D22b0956C0D2573288F82DCc5d6E3a61); + IUwUOracle public constant uwuOracle = IUwUOracle(0xAC4A2aC76D639E10f2C05a41274c1aF85B772598); + IPriceGetter public immutable UNI_V3_TWAP_USDT_ORACLE; + + uint256 public sUSDeScalingFactor = 1047; + address public owner; + + constructor(address _usdeUsdtOracle) public { + owner = msg.sender; + UNI_V3_TWAP_USDT_ORACLE = IPriceGetter(_usdeUsdtOracle); + } + + /******* VIEW *******/ + + function getPrice() external view override returns (uint256) { + (uint256[] memory prices, bool uniFail) = _getPrices(true); + + uint256 median = uniFail ? (prices[5] + prices[6]) / 2 : prices[5]; + + require(median > 0, 'Median is zero'); + + return FullMath.mulDiv(median, sUSDeScalingFactor, 1e3); + } + + function getPrices(bool sorted) external view returns (uint256[] memory, bool) { + // ruleid: oracle-uses-curve-spot-price + return _getPrices(sorted); + } + + function getMedianUsdePriceInUSD() external view returns (uint256) { + (uint256[] memory prices, bool uniFail) = _getPrices(true); + + return uniFail ? (prices[5] + prices[6]) / 2 : prices[5]; + } + + function getUSDeFraxEMAInUSD() external view returns (uint256, uint256) { + // ruleid: oracle-uses-curve-spot-price + return _getUSDeFraxEMAInUSD(); + } + + function getUSDeUSDCEMAInUSD() external view returns (uint256, uint256) { + // ruleid: oracle-uses-curve-spot-price + return _getUSDeUsdcEMAInUSD(); + } + + function getUSDeDAIEMAInUSD() external view returns (uint256, uint256) { + // ruleid: oracle-uses-curve-spot-price + return _getUSDeDaiEMAInUSD(); + } + + function getUSDeCrvUsdEMAInUSD() external view returns (uint256, uint256) { + // ruleid: oracle-uses-curve-spot-price + return _getCrvUsdUSDeEMAInUSD(); + } + + function getUSDeGhoEMAInUSD() external view returns (uint256, uint256) { + // ruleid: oracle-uses-curve-spot-price + return _getUSDeGhoEMAInUSD(); + } + + function getUSDeUsdtTWAPInUSD() external view returns (uint256) { + try UNI_V3_TWAP_USDT_ORACLE.getPrice() returns (uint256 price) { + return price; + } catch { + return 0; + } + } + + /******* OWNER *******/ + + function changeScalingFactor(uint256 _newScalingFactor) external { + require(msg.sender == owner, 'Only Owner'); + require(_newScalingFactor >= 1000, 'Factor cannot be lower than 1000'); + sUSDeScalingFactor = _newScalingFactor; + } + + function transferOwnership(address _newOwner) external { + require(msg.sender == owner, 'Only Owner'); + owner = _newOwner; + } + + /******* INTERNAL *******/ + + function _getPrices(bool sorted) internal view returns (uint256[] memory, bool uniFail) { + uint256[] memory prices = new uint256[](11); + (prices[0], prices[1]) = _getUSDeFraxEMAInUSD(); + (prices[2], prices[3]) = _getUSDeUsdcEMAInUSD(); + (prices[4], prices[5]) = _getUSDeDaiEMAInUSD(); + (prices[6], prices[7]) = _getCrvUsdUSDeEMAInUSD(); + (prices[8], prices[9]) = _getUSDeGhoEMAInUSD(); + try UNI_V3_TWAP_USDT_ORACLE.getPrice() returns (uint256 price) { + prices[10] = price; + } catch { + uniFail = true; + } + + if (sorted) { + _bubbleSort(prices); + } + // ruleid: oracle-uses-curve-spot-price + return (prices, uniFail); + } + + function _getUSDeFraxEMAInUSD() internal view returns (uint256, uint256) { + uint256 price = uwuOracle.getAssetPrice(FRAX); + // (USDe/FRAX * FRAX/USD) / 1e18 + // ruleid: oracle-uses-curve-spot-price + return ( + FullMath.mulDiv(FRAX_POOL.price_oracle(0), price, 1e18), + FullMath.mulDiv(FRAX_POOL.get_p(0), price, 1e18) + ); + } + + function _getUSDeUsdcEMAInUSD() internal view returns (uint256, uint256) { + uint256 price = uwuOracle.getAssetPrice(USDC); + // (USDC/USD * 1e18) / USDC/USDe + // ruleid: oracle-uses-curve-spot-price + return ( + FullMath.mulDiv(price, 1e18, USDC_POOL.price_oracle(0)), + FullMath.mulDiv(price, 1e18, USDC_POOL.get_p(0)) + ); + } + + function _getUSDeDaiEMAInUSD() internal view returns (uint256, uint256) { + uint256 price = uwuOracle.getAssetPrice(DAI); + // (DAI/USD * 1e18) / DAI/USDe + // ruleid: oracle-uses-curve-spot-price + return ( + FullMath.mulDiv(price, 1e18, DAI_POOL.price_oracle(0)), + FullMath.mulDiv(price, 1e18, DAI_POOL.get_p(0)) + ); + } + + function _getCrvUsdUSDeEMAInUSD() internal view returns (uint256, uint256) { + uint256 price = uwuOracle.getAssetPrice(CRVUSD); + // (CRVUSD/USD * 1e18) / CRVUSD/USDe + // ruleid: oracle-uses-curve-spot-price + return ( + FullMath.mulDiv(price, 1e18, CRVUSD_POOL.price_oracle(0)), + FullMath.mulDiv(price, 1e18, CRVUSD_POOL.get_p(0)) + ); + } + + function _getUSDeGhoEMAInUSD() internal view returns (uint256, uint256) { + uint256 price = uwuOracle.getAssetPrice(GHO); + // (USDe/GHO * GHO/USD) / 1e18 + // ruleid: oracle-uses-curve-spot-price + return ( + FullMath.mulDiv(GHO_POOL.price_oracle(0), price, 1e18), + FullMath.mulDiv(GHO_POOL.get_p(0), price, 1e18) + ); + } + + function _bubbleSort(uint[] memory arr) internal pure returns (uint[] memory) { + uint256 n = arr.length; + for (uint256 i = 0; i < n - 1; i++) { + for (uint256 j = 0; j < n - i - 1; j++) { + if (arr[j] > arr[j + 1]) { + (arr[j], arr[j + 1]) = (arr[j + 1], arr[j]); + } + } + } + // ruleid: oracle-uses-curve-spot-price + return arr; + } +} diff --git a/semgrep-rules/security/oracle-uses-curve-spot-price.yaml b/semgrep-rules/security/oracle-uses-curve-spot-price.yaml new file mode 100644 index 0000000..2d078a7 --- /dev/null +++ b/semgrep-rules/security/oracle-uses-curve-spot-price.yaml @@ -0,0 +1,34 @@ +rules: + - id: oracle-uses-curve-spot-price + message: Oracle uses the get_p() Curve pool function which can be manipulated via flashloan to calculate the asset price + metadata: + category: security + technology: + - solidity + cwe: "CWE-682: Incorrect Calculation" + confidence: LOW + likelihood: LOW + impact: HIGH + subcategory: + - vuln + references: + - https://x.com/danielvf/status/1800556249924440211 + - https://slowmist.medium.com/analysis-of-the-uwu-lend-hack-9502b2c06dbe + - https://defimon.xyz/attacker/mainnet/0x841ddf093f5188989fa1524e7b893de64b421f47 + - https://x.com/CyversAlerts/status/1800139071857316328 + - https://docs.curve.fi/stableswap-exchange/stableswap-ng/pools/oracles/?h=get_p#price-and-d-oracles + mode: taint + pattern-sources: + - patterns: + - pattern-either: + - pattern: $CONTRACT.get_p($I) + - pattern: $CONTRACT.get_p() + - metavariable-regex: + metavariable: $I + regex: "[0-9]" + pattern-sinks: + - patterns: + - pattern: return ...; + languages: + - solidity + severity: ERROR diff --git a/semgrep-rules/security/proxy-storage-collision.sol b/semgrep-rules/security/proxy-storage-collision.sol new file mode 100644 index 0000000..0d03b0e --- /dev/null +++ b/semgrep-rules/security/proxy-storage-collision.sol @@ -0,0 +1,189 @@ +pragma solidity ^0.5.0; + +import "@openzeppelin/upgrades/contracts/upgradeability/UpgradeabilityProxy.sol"; + + +/** + * @notice Wrapper around OpenZeppelin's UpgradeabilityProxy contract. + * Permissions proxy upgrade logic to Audius Governance contract. + * https://github.com/OpenZeppelin/openzeppelin-sdk/blob/release/2.8/packages/lib/contracts/upgradeability/UpgradeabilityProxy.sol + * @dev Any logic contract that has a signature clash with this proxy contract will be unable to call those functions + * Please ensure logic contract functions do not share a signature with any functions defined in this file + */ +// ruleid: proxy-storage-collision +contract AudiusAdminUpgradeabilityProxy is UpgradeabilityProxy { + address private proxyAdmin; + string private constant ERROR_ONLY_ADMIN = ( + "AudiusAdminUpgradeabilityProxy: Caller must be current proxy admin" + ); + + /** + * @notice Sets admin address for future upgrades + * @param _logic - address of underlying logic contract. + * Passed to UpgradeabilityProxy constructor. + * @param _proxyAdmin - address of proxy admin + * Set to governance contract address for all non-governance contracts + * Governance is deployed and upgraded to have own address as admin + * @param _data - data of function to be called on logic contract. + * Passed to UpgradeabilityProxy constructor. + */ + constructor( + address _logic, + address _proxyAdmin, + bytes memory _data + ) + UpgradeabilityProxy(_logic, _data) public payable + { + proxyAdmin = _proxyAdmin; + } + + /** + * @notice Upgrade the address of the logic contract for this proxy + * @dev Recreation of AdminUpgradeabilityProxy._upgradeTo. + * Adds a check to ensure msg.sender is the Audius Proxy Admin + * @param _newImplementation - new address of logic contract that the proxy will point to + */ + function upgradeTo(address _newImplementation) external { + require(msg.sender == proxyAdmin, ERROR_ONLY_ADMIN); + _upgradeTo(_newImplementation); + } + + /** + * @return Current proxy admin address + */ + function getAudiusProxyAdminAddress() external view returns (address) { + return proxyAdmin; + } + + /** + * @return The address of the implementation. + */ + function implementation() external view returns (address) { + return _implementation(); + } + + /** + * @notice Set the Audius Proxy Admin + * @dev Only callable by current proxy admin address + * @param _adminAddress - new admin address + */ + function setAudiusProxyAdminAddress(address _adminAddress) external { + require(msg.sender == proxyAdmin, ERROR_ONLY_ADMIN); + proxyAdmin = _adminAddress; + } +} + +/** + * @notice This contract implements a proxy that is upgradeable by an admin. + */ +// ok: proxy-storage-collision +contract VaultTransparentUpgradeableProxy is TransparentUpgradeableProxy, IVaultImmutable { + /// @notice Vault underlying asset + IERC20 public override immutable underlying; + + /// @notice Vault risk provider address + address public override immutable riskProvider; + + /// @notice A number from -10 to 10 indicating the risk tolerance of the vault + int8 public override immutable riskTolerance; + + /** + * @notice Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`. + */ + constructor( + address _logic, + address admin_, + VaultImmutables memory vaultImmutables + ) TransparentUpgradeableProxy(_logic, admin_, "") payable { + underlying = vaultImmutables.underlying; + riskProvider = vaultImmutables.riskProvider; + riskTolerance = vaultImmutables.riskTolerance; + } +} + +/** + * @notice This contract implements a proxy that is upgradeable by an admin. + */ +// ruleid: proxy-storage-collision +contract VaultTransparentUpgradeableProxy is TransparentUpgradeableProxy, IVaultImmutable { + /// @notice Vault underlying asset + address private proxyAdmin = address(0); + IERC20 public override immutable underlying; + + /// @notice Vault risk provider address + address public override immutable riskProvider; + + /// @notice A number from -10 to 10 indicating the risk tolerance of the vault + int8 public override immutable riskTolerance; + + /** + * @notice Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`. + */ + constructor( + address _logic, + address admin_, + VaultImmutables memory vaultImmutables + ) TransparentUpgradeableProxy(_logic, admin_, "") payable { + underlying = vaultImmutables.underlying; + riskProvider = vaultImmutables.riskProvider; + riskTolerance = vaultImmutables.riskTolerance; + } +} + +/** + * @notice This contract implements a proxy that is upgradeable by an admin. + */ +// ok: proxy-storage-collision +contract VaultTransparentUpgradeableProxy is TransparentUpgradeableProxy, IVaultImmutable { + /// @notice Vault underlying asset + address constant proxyAdmin = address(0); + IERC20 public override immutable underlying; + + /// @notice Vault risk provider address + address public override immutable riskProvider; + + /// @notice A number from -10 to 10 indicating the risk tolerance of the vault + int8 public override immutable riskTolerance; + + /** + * @notice Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`. + */ + constructor( + address _logic, + address admin_, + VaultImmutables memory vaultImmutables + ) TransparentUpgradeableProxy(_logic, admin_, "") payable { + underlying = vaultImmutables.underlying; + riskProvider = vaultImmutables.riskProvider; + riskTolerance = vaultImmutables.riskTolerance; + } +} + +/** + * @notice This contract implements a proxy that is upgradeable by an admin. + */ +// ok: proxy-storage-collision +contract VaultTransparentUpgradeableProxy is TransparentUpgradeableProxy, IVaultImmutable { + /// @notice Vault underlying asset + address immutable proxyAdmin = address(0); + IERC20 public override immutable underlying; + + /// @notice Vault risk provider address + address public override immutable riskProvider; + + /// @notice A number from -10 to 10 indicating the risk tolerance of the vault + int8 public override immutable riskTolerance; + + /** + * @notice Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`. + */ + constructor( + address _logic, + address admin_, + VaultImmutables memory vaultImmutables + ) TransparentUpgradeableProxy(_logic, admin_, "") payable { + underlying = vaultImmutables.underlying; + riskProvider = vaultImmutables.riskProvider; + riskTolerance = vaultImmutables.riskTolerance; + } +} \ No newline at end of file diff --git a/semgrep-rules/security/proxy-storage-collision.yaml b/semgrep-rules/security/proxy-storage-collision.yaml new file mode 100644 index 0000000..be8dd82 --- /dev/null +++ b/semgrep-rules/security/proxy-storage-collision.yaml @@ -0,0 +1,75 @@ +rules: + - + id: proxy-storage-collision + message: Proxy declares a state var that may override a storage slot of the implementation + metadata: + category: security + technology: + - solidity + cwe: "CWE-787: Out-of-bounds Write" + confidence: HIGH + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://blog.audius.co/article/audius-governance-takeover-post-mortem-7-23-22 + patterns: + - pattern-either: + - pattern: | + contract $CONTRACT is ..., $PROXY, ... { + ... + $TYPE $VAR; + ... + constructor(...) { + ... + } + ... + } + - pattern: | + contract $CONTRACT is ..., $PROXY, ... { + ... + $TYPE $VAR = ...; + ... + constructor(...) { + ... + } + ... + } + - pattern-not: | + contract $CONTRACT is ..., $PROXY, ... { + $TYPE immutable $VAR; + ... + constructor(...) { + ... + } + ... + } + - pattern-not: | + contract $CONTRACT is ..., $PROXY, ... { + $TYPE immutable $VAR = ...; + ... + constructor(...) { + ... + } + ... + } + - pattern-not: | + contract $CONTRACT is ..., $PROXY, ... { + $TYPE constant $VAR = ...; + ... + constructor(...) { + ... + } + ... + } + - metavariable-regex: + metavariable: $CONTRACT + regex: ^(?!AdminUpgradeabilityProxy|OwnedUpgrade*abilityProxy).*$ + - metavariable-regex: + metavariable: $PROXY + regex: (UpgradeabilityProxy|AdminUpgradeabilityProxy|OwnedUpgrade*abilityProxy|TransparentUpgradeableProxy|ERC1967Proxy) + - focus-metavariable: $PROXY + languages: + - solidity + severity: WARNING \ No newline at end of file diff --git a/semgrep-rules/security/public-transfer-fees-supporting-tax-tokens.sol b/semgrep-rules/security/public-transfer-fees-supporting-tax-tokens.sol new file mode 100644 index 0000000..0335e37 --- /dev/null +++ b/semgrep-rules/security/public-transfer-fees-supporting-tax-tokens.sol @@ -0,0 +1,868 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import "./LeetSwapV2Fees.sol"; +import "./ILeetSwapV2Factory.sol"; +import "./ILeetSwapV2Pair.sol"; +import "./ILeetSwapV2Callee.sol"; +import "./Math.sol"; +import "./IERC20Metadata.sol"; + +// The base pair of pools, either stable or volatile +contract LeetSwapV2Pair is ILeetSwapV2Pair { + uint8 public constant decimals = 18; + + // Used to denote stable or volatile pair, not immutable since construction happens in the initialize method for CREATE2 deterministic addresses + bool public immutable stable; + + uint256 public totalSupply = 0; + + mapping(address => mapping(address => uint256)) public allowance; + mapping(address => uint256) public balanceOf; + + bytes32 internal DOMAIN_SEPARATOR; + // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + bytes32 internal constant PERMIT_TYPEHASH = + 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; + mapping(address => uint256) public nonces; + + uint256 public constant MINIMUM_LIQUIDITY = 10**3; + + address public immutable token0; + address public immutable token1; + address public immutable fees; + address public immutable factory; + + // Structure to capture time period observations every 30 minutes, used for local oracles + struct Observation { + uint256 timestamp; + uint256 reserve0Cumulative; + uint256 reserve1Cumulative; + } + + // Capture oracle reading every 30 minutes + uint256 constant periodSize = 1800; + + Observation[] public observations; + + uint256 public reserve0; + uint256 public reserve1; + uint256 public blockTimestampLast; + + uint256 public reserve0CumulativeLast; + uint256 public reserve1CumulativeLast; + + // index0 and index1 are used to accumulate fees, this is split out from normal trades to keep the swap "clean" + // this further allows LP holders to easily claim fees for tokens they have/staked + uint256 public index0 = 0; + uint256 public index1 = 0; + + // position assigned to each LP to track their current index0 & index1 vs the global position + mapping(address => uint256) public supplyIndex0; + mapping(address => uint256) public supplyIndex1; + + // tracks the amount of unclaimed, but claimable tokens off of fees for token0 and token1 + mapping(address => uint256) public claimable0; + mapping(address => uint256) public claimable1; + + event Fees(address indexed sender, uint256 amount0, uint256 amount1); + 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(uint256 reserve0, uint256 reserve1); + event Claim( + address indexed sender, + address indexed recipient, + uint256 amount0, + uint256 amount1 + ); + + event Transfer(address indexed from, address indexed to, uint256 amount); + event Approval( + address indexed owner, + address indexed spender, + uint256 amount + ); + + error DEXPaused(); + error InvalidToken(); + error TransferFailed(); + error InsufficientOutputAmount(); + error InsufficientInputAmount(); + error InsufficientLiquidity(); + error ReentrancyGuard(); + error DeadlineExpired(); + error InsufficientLiquidityMinted(); + error InsufficientLiquidityBurned(); + error InvariantNotRespected(); + error InvalidSwapRecipient(); + error InvalidSignature(); + + constructor() { + factory = msg.sender; + (address _token0, address _token1, bool _stable) = ILeetSwapV2Factory( + msg.sender + ).getInitializable(); + (token0, token1, stable) = (_token0, _token1, _stable); + + fees = address(new LeetSwapV2Fees(_token0, _token1)); + + observations.push(Observation(block.timestamp, 0, 0)); + } + + function decimals0() internal view returns (uint256) { + return 10**IERC20Metadata(token0).decimals(); + } + + function decimals1() internal view returns (uint256) { + return 10**IERC20Metadata(token1).decimals(); + } + + function name() public view returns (string memory) { + if (stable) { + return + string( + abi.encodePacked( + "LeetSwapV2 StableV1 Pair - ", + IERC20Metadata(token0).symbol(), + "/", + IERC20Metadata(token1).symbol() + ) + ); + } + + return + string( + abi.encodePacked( + "LeetSwapV2 VolatileV1 Pair - ", + IERC20Metadata(token0).symbol(), + "/", + IERC20Metadata(token1).symbol() + ) + ); + } + + function symbol() public view returns (string memory) { + if (stable) { + return + string( + abi.encodePacked( + "sLS2-", + IERC20Metadata(token0).symbol(), + "/", + IERC20Metadata(token1).symbol() + ) + ); + } + + return + string( + abi.encodePacked( + "vLS2-", + IERC20Metadata(token0).symbol(), + "/", + IERC20Metadata(token1).symbol() + ) + ); + } + + // simple re-entrancy check + uint256 internal _unlocked = 1; + modifier lock() { + if (_unlocked != 1) revert ReentrancyGuard(); + _unlocked = 2; + _; + _unlocked = 1; + } + + function observationLength() external view returns (uint256) { + return observations.length; + } + + function lastObservation() public view returns (Observation memory) { + return observations[observations.length - 1]; + } + + function metadata() + external + view + returns ( + uint256 dec0, + uint256 dec1, + uint256 r0, + uint256 r1, + bool st, + address t0, + address t1 + ) + { + return ( + decimals0(), + decimals1(), + reserve0, + reserve1, + stable, + token0, + token1 + ); + } + + function tokens() external view returns (address, address) { + return (token0, token1); + } + + // claim accumulated but unclaimed fees (viewable via claimable0 and claimable1) + function claimFees() external returns (uint256 claimed0, uint256 claimed1) { + return claimFeesFor(msg.sender); + } + + function claimFeesFor(address recipient) + public + lock + returns (uint256 claimed0, uint256 claimed1) + { + _updateFor(recipient); + + claimed0 = claimable0[recipient]; + claimed1 = claimable1[recipient]; + + claimable0[recipient] = 0; + claimable1[recipient] = 0; + + LeetSwapV2Fees(fees).claimFeesFor(recipient, claimed0, claimed1); + + emit Claim(msg.sender, recipient, claimed0, claimed1); + } + + function claimableFeesFor(address account) + public + view + returns (uint256 _claimable0, uint256 _claimable1) + { + uint256 _supplied = balanceOf[account]; + _claimable0 = claimable0[account]; + _claimable1 = claimable1[account]; + if (_supplied > 0) { + uint256 _delta0 = index0 - supplyIndex0[account]; + uint256 _delta1 = index1 - supplyIndex1[account]; + if (_delta0 > 0) { + uint256 _share = (_supplied * _delta0) / 1e18; + _claimable0 += _share; + } + if (_delta1 > 0) { + uint256 _share = (_supplied * _delta1) / 1e18; + _claimable1 += _share; + } + } + } + + function claimableFees() + external + view + returns (uint256 _claimable0, uint256 _claimable1) + { + return claimableFeesFor(msg.sender); + } + + // Used to transfer fees when calling _update[01] + //ruleid: public-transfer-fees-supporting-tax-tokens + function _transferFeesSupportingTaxTokens(address token, uint256 amount) + public + returns (uint256) + { + if (amount == 0) { + return 0; + } + + uint256 balanceBefore = IERC20(token).balanceOf(fees); + _safeTransfer(token, fees, amount); + uint256 balanceAfter = IERC20(token).balanceOf(fees); + + return balanceAfter - balanceBefore; + } + + // Accrue fees on token0 + function _update0(uint256 amount) internal { + uint256 _protocolFeesShare = ILeetSwapV2Factory(factory) + .protocolFeesShare(); + address _protocolFeesRecipient = ILeetSwapV2Factory(factory) + .protocolFeesRecipient(); + uint256 _protocolFeesAmount = (amount * _protocolFeesShare) / 10000; + amount = _transferFeesSupportingTaxTokens( + token0, + amount - _protocolFeesAmount + ); + if (_protocolFeesAmount > 0) + _safeTransfer(token0, _protocolFeesRecipient, _protocolFeesAmount); + uint256 _ratio = (amount * 1e18) / totalSupply; + if (_ratio > 0) { + index0 += _ratio; + } + emit Fees(msg.sender, amount, 0); + } + + // Accrue fees on token1 + function _update1(uint256 amount) internal { + uint256 _protocolFeesShare = ILeetSwapV2Factory(factory) + .protocolFeesShare(); + address _protocolFeesRecipient = ILeetSwapV2Factory(factory) + .protocolFeesRecipient(); + uint256 _protocolFeesAmount = (amount * _protocolFeesShare) / 10000; + amount = _transferFeesSupportingTaxTokens( + token1, + amount - _protocolFeesAmount + ); + if (_protocolFeesAmount > 0) + _safeTransfer(token1, _protocolFeesRecipient, _protocolFeesAmount); + uint256 _ratio = (amount * 1e18) / totalSupply; + if (_ratio > 0) { + index1 += _ratio; + } + emit Fees(msg.sender, 0, amount); + } + + // this function MUST be called on any balance changes, otherwise can be used to infinitely claim fees + // Fees are segregated from core funds, so fees can never put liquidity at risk + function _updateFor(address recipient) internal { + uint256 _supplied = balanceOf[recipient]; // get LP balance of `recipient` + if (_supplied > 0) { + uint256 _supplyIndex0 = supplyIndex0[recipient]; // get last adjusted index0 for recipient + uint256 _supplyIndex1 = supplyIndex1[recipient]; + uint256 _index0 = index0; // get global index0 for accumulated fees + uint256 _index1 = index1; + supplyIndex0[recipient] = _index0; // update user current position to global position + supplyIndex1[recipient] = _index1; + uint256 _delta0 = _index0 - _supplyIndex0; // see if there is any difference that need to be accrued + uint256 _delta1 = _index1 - _supplyIndex1; + if (_delta0 > 0) { + uint256 _share = (_supplied * _delta0) / 1e18; // add accrued difference for each supplied token + claimable0[recipient] += _share; + } + if (_delta1 > 0) { + uint256 _share = (_supplied * _delta1) / 1e18; + claimable1[recipient] += _share; + } + } else { + supplyIndex0[recipient] = index0; // new users are set to the default global state + supplyIndex1[recipient] = index1; + } + } + + function getReserves() + public + view + returns ( + uint256 _reserve0, + uint256 _reserve1, + uint256 _blockTimestampLast + ) + { + _reserve0 = reserve0; + _reserve1 = reserve1; + _blockTimestampLast = blockTimestampLast; + } + + // update reserves and, on the first call per block, price accumulators + function _update( + uint256 balance0, + uint256 balance1, + uint256 _reserve0, + uint256 _reserve1 + ) internal { + uint256 blockTimestamp = block.timestamp; + uint256 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired + if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) { + reserve0CumulativeLast += _reserve0 * timeElapsed; + reserve1CumulativeLast += _reserve1 * timeElapsed; + } + + Observation memory _point = lastObservation(); + timeElapsed = blockTimestamp - _point.timestamp; // compare the last observation with current timestamp, if greater than 30 minutes, record a new event + if (timeElapsed > periodSize) { + observations.push( + Observation( + blockTimestamp, + reserve0CumulativeLast, + reserve1CumulativeLast + ) + ); + } + reserve0 = balance0; + reserve1 = balance1; + blockTimestampLast = blockTimestamp; + emit Sync(reserve0, reserve1); + } + + // produces the cumulative price using counterfactuals to save gas and avoid a call to sync. + function currentCumulativePrices() + public + view + returns ( + uint256 reserve0Cumulative, + uint256 reserve1Cumulative, + uint256 blockTimestamp + ) + { + blockTimestamp = block.timestamp; + reserve0Cumulative = reserve0CumulativeLast; + reserve1Cumulative = reserve1CumulativeLast; + + // if time has elapsed since the last update on the pair, mock the accumulated price values + ( + uint256 _reserve0, + uint256 _reserve1, + uint256 _blockTimestampLast + ) = getReserves(); + if (_blockTimestampLast != blockTimestamp) { + // subtraction overflow is desired + uint256 timeElapsed = blockTimestamp - _blockTimestampLast; + reserve0Cumulative += _reserve0 * timeElapsed; + reserve1Cumulative += _reserve1 * timeElapsed; + } + } + + // gives the current twap price measured from amountIn * tokenIn gives amountOut + function current(address tokenIn, uint256 amountIn) + external + view + returns (uint256 amountOut) + { + Observation memory _observation = lastObservation(); + ( + uint256 reserve0Cumulative, + uint256 reserve1Cumulative, + + ) = currentCumulativePrices(); + if (block.timestamp == _observation.timestamp) { + _observation = observations[observations.length - 2]; + } + + uint256 timeElapsed = block.timestamp - _observation.timestamp; + uint256 _reserve0 = (reserve0Cumulative - + _observation.reserve0Cumulative) / timeElapsed; + uint256 _reserve1 = (reserve1Cumulative - + _observation.reserve1Cumulative) / timeElapsed; + amountOut = _getAmountOut(amountIn, tokenIn, _reserve0, _reserve1); + } + + // as per `current`, however allows user configured granularity, up to the full window size + function quote( + address tokenIn, + uint256 amountIn, + uint256 granularity + ) external view returns (uint256 amountOut) { + uint256[] memory _prices = sample(tokenIn, amountIn, granularity, 1); + uint256 priceAverageCumulative; + for (uint256 i = 0; i < _prices.length; i++) { + priceAverageCumulative += _prices[i]; + } + return priceAverageCumulative / granularity; + } + + // returns a memory set of twap prices + function prices( + address tokenIn, + uint256 amountIn, + uint256 points + ) external view returns (uint256[] memory) { + return sample(tokenIn, amountIn, points, 1); + } + + function sample( + address tokenIn, + uint256 amountIn, + uint256 points, + uint256 window + ) public view returns (uint256[] memory) { + uint256[] memory _prices = new uint256[](points); + + uint256 length = observations.length - 1; + uint256 i = length - (points * window); + uint256 nextIndex = 0; + uint256 index = 0; + + for (; i < length; i += window) { + nextIndex = i + window; + uint256 timeElapsed = observations[nextIndex].timestamp - + observations[i].timestamp; + uint256 _reserve0 = (observations[nextIndex].reserve0Cumulative - + observations[i].reserve0Cumulative) / timeElapsed; + uint256 _reserve1 = (observations[nextIndex].reserve1Cumulative - + observations[i].reserve1Cumulative) / timeElapsed; + _prices[index] = _getAmountOut( + amountIn, + tokenIn, + _reserve0, + _reserve1 + ); + index = index + 1; + } + return _prices; + } + + // this low-level function should be called from a contract which performs important safety checks + // standard uniswap v2 implementation + function mint(address to) external lock returns (uint256 liquidity) { + (uint256 _reserve0, uint256 _reserve1) = (reserve0, reserve1); + uint256 _balance0 = IERC20Metadata(token0).balanceOf(address(this)); + uint256 _balance1 = IERC20Metadata(token1).balanceOf(address(this)); + uint256 _amount0 = _balance0 - _reserve0; + uint256 _amount1 = _balance1 - _reserve1; + + uint256 _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee + if (_totalSupply == 0) { + liquidity = Math.sqrt(_amount0 * _amount1) - MINIMUM_LIQUIDITY; + _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens + } else { + liquidity = Math.min( + (_amount0 * _totalSupply) / _reserve0, + (_amount1 * _totalSupply) / _reserve1 + ); + } + if (liquidity <= 0) revert InsufficientLiquidityMinted(); + _mint(to, liquidity); + + _update(_balance0, _balance1, _reserve0, _reserve1); + emit Mint(msg.sender, _amount0, _amount1); + } + + // this low-level function should be called from a contract which performs important safety checks + // standard uniswap v2 implementation + function burn(address to) + external + lock + returns (uint256 amount0, uint256 amount1) + { + (uint256 _reserve0, uint256 _reserve1) = (reserve0, reserve1); + (address _token0, address _token1) = (token0, token1); + uint256 _balance0 = IERC20Metadata(_token0).balanceOf(address(this)); + uint256 _balance1 = IERC20Metadata(_token1).balanceOf(address(this)); + uint256 _liquidity = balanceOf[address(this)]; + + uint256 _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee + amount0 = (_liquidity * _balance0) / _totalSupply; // using balances ensures pro-rata distribution + amount1 = (_liquidity * _balance1) / _totalSupply; // using balances ensures pro-rata distribution + if (amount0 <= 0 || amount1 <= 0) revert InsufficientLiquidityBurned(); + _burn(address(this), _liquidity); + _safeTransfer(_token0, to, amount0); + _safeTransfer(_token1, to, amount1); + _balance0 = IERC20Metadata(_token0).balanceOf(address(this)); + _balance1 = IERC20Metadata(_token1).balanceOf(address(this)); + + _update(_balance0, _balance1, _reserve0, _reserve1); + emit Burn(msg.sender, amount0, amount1, to); + } + + // this low-level function should be called from a contract which performs important safety checks + function swap( + uint256 amount0Out, + uint256 amount1Out, + address to, + bytes calldata data + ) external lock { + if (ILeetSwapV2Factory(factory).isPaused()) revert DEXPaused(); + if (amount0Out <= 0 && amount1Out <= 0) + revert InsufficientOutputAmount(); + (uint256 _reserve0, uint256 _reserve1) = (reserve0, reserve1); + if (amount0Out >= _reserve0 || amount1Out >= _reserve1) + revert InsufficientLiquidity(); + + uint256 _balance0; + uint256 _balance1; + { + // scope for _token{0,1}, avoids stack too deep errors + (address _token0, address _token1) = (token0, token1); + if (to == _token0 || to == _token1) revert InvalidSwapRecipient(); + if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens + if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens + if (data.length > 0) + ILeetSwapV2Callee(to).hook( + msg.sender, + amount0Out, + amount1Out, + data + ); // callback, used for flash loans + _balance0 = IERC20Metadata(_token0).balanceOf(address(this)); + _balance1 = IERC20Metadata(_token1).balanceOf(address(this)); + } + uint256 amount0In = _balance0 > _reserve0 - amount0Out + ? _balance0 - (_reserve0 - amount0Out) + : 0; + uint256 amount1In = _balance1 > _reserve1 - amount1Out + ? _balance1 - (_reserve1 - amount1Out) + : 0; + if (amount0In <= 0 && amount1In <= 0) revert InsufficientInputAmount(); + { + // scope for reserve{0,1}Adjusted, avoids stack too deep errors + (address _token0, address _token1) = (token0, token1); + uint256 _tradingFees = ILeetSwapV2Factory(factory).tradingFees( + address(this), + to + ); + if (amount0In > 0) _update0((amount0In * _tradingFees) / 10000); // accrue fees for token0 and move them out of pool + if (amount1In > 0) _update1((amount1In * _tradingFees) / 10000); // accrue fees for token1 and move them out of pool + _balance0 = IERC20Metadata(_token0).balanceOf(address(this)); // since we removed tokens, we need to reconfirm balances, can also simply use previous balance - amountIn/ 10000, but doing balanceOf again as safety check + _balance1 = IERC20Metadata(_token1).balanceOf(address(this)); + // The curve, either x3y+y3x for stable pools, or x*y for volatile pools + if (_k(_balance0, _balance1) < _k(_reserve0, _reserve1)) + revert InvariantNotRespected(); + } + + _update(_balance0, _balance1, _reserve0, _reserve1); + emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to); + } + + // force balances to match reserves + function skim(address to) external lock { + (address _token0, address _token1) = (token0, token1); + _safeTransfer( + _token0, + to, + IERC20Metadata(_token0).balanceOf(address(this)) - (reserve0) + ); + _safeTransfer( + _token1, + to, + IERC20Metadata(_token1).balanceOf(address(this)) - (reserve1) + ); + } + + // force reserves to match balances + function sync() external lock { + _update( + IERC20Metadata(token0).balanceOf(address(this)), + IERC20Metadata(token1).balanceOf(address(this)), + reserve0, + reserve1 + ); + } + + function _f(uint256 x0, uint256 y) internal pure returns (uint256) { + return + (x0 * ((((y * y) / 1e18) * y) / 1e18)) / + 1e18 + + (((((x0 * x0) / 1e18) * x0) / 1e18) * y) / + 1e18; + } + + function _d(uint256 x0, uint256 y) internal pure returns (uint256) { + return + (3 * x0 * ((y * y) / 1e18)) / + 1e18 + + ((((x0 * x0) / 1e18) * x0) / 1e18); + } + + function _get_y( + uint256 x0, + uint256 xy, + uint256 y + ) internal pure returns (uint256) { + for (uint256 i = 0; i < 255; i++) { + uint256 y_prev = y; + uint256 k = _f(x0, y); + if (k < xy) { + uint256 dy = ((xy - k) * 1e18) / _d(x0, y); + y = y + dy; + } else { + uint256 dy = ((k - xy) * 1e18) / _d(x0, y); + y = y - dy; + } + if (y > y_prev) { + if (y - y_prev <= 1) { + return y; + } + } else { + if (y_prev - y <= 1) { + return y; + } + } + } + return y; + } + + function getAmountOut( + uint256 amountIn, + address tokenIn, + address to + ) public view returns (uint256) { + (uint256 _reserve0, uint256 _reserve1) = (reserve0, reserve1); + uint256 _tradingFees = ILeetSwapV2Factory(factory).tradingFees( + address(this), + to + ); + amountIn -= (amountIn * _tradingFees) / 10000; // remove fee from amount received + return _getAmountOut(amountIn, tokenIn, _reserve0, _reserve1); + } + + function getAmountOut(uint256 amountIn, address tokenIn) + external + view + returns (uint256) + { + return getAmountOut(amountIn, tokenIn, msg.sender); + } + + function _getAmountOut( + uint256 amountIn, + address tokenIn, + uint256 _reserve0, + uint256 _reserve1 + ) internal view returns (uint256) { + if (stable) { + uint256 xy = _k(_reserve0, _reserve1); + _reserve0 = (_reserve0 * 1e18) / decimals0(); + _reserve1 = (_reserve1 * 1e18) / decimals1(); + (uint256 reserveA, uint256 reserveB) = tokenIn == token0 + ? (_reserve0, _reserve1) + : (_reserve1, _reserve0); + amountIn = tokenIn == token0 + ? (amountIn * 1e18) / decimals0() + : (amountIn * 1e18) / decimals1(); + uint256 y = reserveB - _get_y(amountIn + reserveA, xy, reserveB); + return (y * (tokenIn == token0 ? decimals1() : decimals0())) / 1e18; + } else { + (uint256 reserveA, uint256 reserveB) = tokenIn == token0 + ? (_reserve0, _reserve1) + : (_reserve1, _reserve0); + return (amountIn * reserveB) / (reserveA + amountIn); + } + } + + function _k(uint256 x, uint256 y) internal view returns (uint256) { + if (stable) { + uint256 _x = (x * 1e18) / decimals0(); + uint256 _y = (y * 1e18) / decimals1(); + uint256 _a = (_x * _y) / 1e18; + uint256 _b = ((_x * _x) / 1e18 + (_y * _y) / 1e18); + return (_a * _b) / 1e18; // x3y+y3x >= k + } else { + return x * y; // xy >= k + } + } + + function _mint(address dst, uint256 amount) internal { + _updateFor(dst); // balances must be updated on mint/burn/transfer + totalSupply += amount; + balanceOf[dst] += amount; + emit Transfer(address(0), dst, amount); + } + + function _burn(address dst, uint256 amount) internal { + _updateFor(dst); + totalSupply -= amount; + balanceOf[dst] -= amount; + emit Transfer(dst, address(0), amount); + } + + function approve(address spender, uint256 amount) external returns (bool) { + allowance[msg.sender][spender] = amount; + + emit Approval(msg.sender, spender, amount); + return true; + } + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + if (deadline < block.timestamp) revert DeadlineExpired(); + DOMAIN_SEPARATOR = keccak256( + abi.encode( + keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ), + keccak256(bytes(name())), + keccak256(bytes("1")), + block.chainid, + address(this) + ) + ); + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256( + abi.encode( + PERMIT_TYPEHASH, + owner, + spender, + value, + nonces[owner]++, + deadline + ) + ) + ) + ); + address recoveredAddress = ecrecover(digest, v, r, s); + if (recoveredAddress == address(0) || recoveredAddress != owner) + revert InvalidSignature(); + allowance[owner][spender] = value; + + emit Approval(owner, spender, value); + } + + function transfer(address dst, uint256 amount) external returns (bool) { + _transferTokens(msg.sender, dst, amount); + return true; + } + + function transferFrom( + address src, + address dst, + uint256 amount + ) external returns (bool) { + address spender = msg.sender; + uint256 spenderAllowance = allowance[src][spender]; + + if (spender != src && spenderAllowance != type(uint256).max) { + uint256 newAllowance = spenderAllowance - amount; + allowance[src][spender] = newAllowance; + + emit Approval(src, spender, newAllowance); + } + + _transferTokens(src, dst, amount); + return true; + } + + function _transferTokens( + address src, + address dst, + uint256 amount + ) internal { + _updateFor(src); // update fee position for src + _updateFor(dst); // update fee position for dst + + balanceOf[src] -= amount; + balanceOf[dst] += amount; + + emit Transfer(src, dst, amount); + } + + function _safeTransfer( + address token, + address to, + uint256 value + ) internal { + if (token.code.length == 0) revert InvalidToken(); + bool success = IERC20(token).transfer(to, value); + if (!success) revert TransferFailed(); + } +} \ No newline at end of file diff --git a/semgrep-rules/security/public-transfer-fees-supporting-tax-tokens.yaml b/semgrep-rules/security/public-transfer-fees-supporting-tax-tokens.yaml new file mode 100644 index 0000000..3222d86 --- /dev/null +++ b/semgrep-rules/security/public-transfer-fees-supporting-tax-tokens.yaml @@ -0,0 +1,29 @@ +rules: + - + id: public-transfer-fees-supporting-tax-tokens + message: public _transferFeesSupportingTaxTokens() without any modificators + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: LOW + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/BlockSecTeam/status/1686217464051539968 + patterns: + - pattern-either: + - pattern: | + function _transferFeesSupportingTaxTokens(...) public { ... } + - pattern: | + function _transferFeesSupportingTaxTokens(...) external { ... } + - pattern-not: | + function _transfer(...) $M { ... } + - pattern-not: | + function _transfer(...) $M(...) { ... } + languages: + - solidity + severity: WARNING diff --git a/semgrep-rules/security/redacted-cartel-custom-approval-bug.sol b/semgrep-rules/security/redacted-cartel-custom-approval-bug.sol new file mode 100644 index 0000000..9d0bc19 --- /dev/null +++ b/semgrep-rules/security/redacted-cartel-custom-approval-bug.sol @@ -0,0 +1,945 @@ +/** + *Submitted for verification at Etherscan.io on 2021-12-28 +*/ + +// SPDX-License-Identifier: MIT +pragma solidity 0.7.5; + +/** + * @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); + } + } + } +} + +/** + * @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 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) { + _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(msg.sender, 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(msg.sender, 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, msg.sender, _allowances[sender][msg.sender].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(msg.sender, spender, _allowances[msg.sender][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(msg.sender, spender, _allowances[msg.sender][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 { } +} + +/** + * @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 IStaking { + function stake( uint _amount, address _recipient ) external returns ( bool ); + + function claim( address recipient ) external; + + function unstake( uint _amount, bool _trigger ) external; + + function index() external view returns ( uint ); +} + +interface IxBTRFLY { + function balanceForGons( uint gons ) external view returns ( uint ); + + function gonsForBalance( uint amount ) external view returns ( uint ); +} + +interface IOwnable { + function owner() external view returns (address); + + function renounceOwnership() external; + + function transferOwnership( address newOwner_ ) external; +} + +contract Ownable is IOwnable { + + address internal _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + constructor () { + _owner = msg.sender; + emit OwnershipTransferred( address(0), _owner ); + } + + function owner() public view override returns (address) { + return _owner; + } + + modifier onlyOwner() { + require( _owner == msg.sender, "Ownable: caller is not the owner" ); + _; + } + + function renounceOwnership() public virtual override onlyOwner() { + emit OwnershipTransferred( _owner, address(0) ); + _owner = address(0); + } + + function transferOwnership( address newOwner_ ) public virtual override onlyOwner() { + require( newOwner_ != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred( _owner, newOwner_ ); + _owner = newOwner_; + } +} + +abstract contract FrozenToken is ERC20, Ownable { + using SafeERC20 for ERC20; + using SafeMath for uint256; + + bool public isTokenFrozen = true; + mapping (address => bool ) public isAuthorisedOperators; + + + modifier onlyAuthorisedOperators () { + require(!isTokenFrozen || isAuthorisedOperators[msg.sender], 'Frozen: token frozen or msg.sender not authorised'); + _; + } + + + function unFreezeToken () external onlyOwner () { + isTokenFrozen = false; + } + + function changeAuthorisation (address operator, bool status) public onlyOwner { + require(operator != address(0), "Frozen: new operator cant be zero address"); + isAuthorisedOperators[operator] = status; + } + + + function addBatchAuthorisedOperators(address[] memory authorisedOperatorsArray) external onlyOwner { + for (uint i = 0; i < authorisedOperatorsArray.length; i++) { + changeAuthorisation(authorisedOperatorsArray[i],true); + } + } + + + function transfer(address recipient, uint256 amount) public virtual override onlyAuthorisedOperators returns (bool){ + _transfer(msg.sender, recipient, amount); + return true; + } + + function transferFrom(address sender, address recipient, uint256 amount) public virtual override onlyAuthorisedOperators returns (bool) { + _transfer(sender, recipient, amount); + // ruleid: redacted-cartel-custom-approval-bug + _approve(sender, msg.sender, allowance(sender, recipient ).sub(amount, "ERC20: transfer amount exceeds allowance")); + return true; + } + +} + +contract wxBTRFLY is FrozenToken { + using Address for address; + using SafeMath for uint; + + address public immutable staking; + address public immutable BTRFLY; + address public immutable xBTRFLY; + + //DON'T EVER F***ING CHANGE PLS - thank you :) | DOUBLE CHECK on Etherscan to verify number is correct + uint public immutable realINDEX = 23158417847463239084714197001737581570653996933128112807891516 * 1e9; + + constructor( address _staking, address _BTRFLY, address _xBTRFLY ) ERC20( 'wxBTRFLY', 'wxBTRFLY' ) { + require( _staking != address(0) ); + staking = _staking; + require( _BTRFLY != address(0) ); + BTRFLY = _BTRFLY; + require( _xBTRFLY != address(0) ); + xBTRFLY = _xBTRFLY; + } + + /** + @notice stakes BTRFLY and wraps sBTRFLY + @param _amount uint + @return uint + */ + function wrapFromBTRFLY( uint _amount ) external returns ( uint ) { + IERC20( BTRFLY ).transferFrom( msg.sender, address(this), _amount ); + IERC20( BTRFLY ).approve( staking, _amount ); // stake BTRFLY for sBTRFLY + IStaking( staking ).stake( _amount, address(this) ); + IStaking( staking ).claim(address(this)); + + uint value = wBTRFLYValue( _amount ); + _mint( msg.sender, value ); + return value; + } + + /** + @notice unwrap sBTRFLY and unstake BTRFLY + @param _amount uint + @return uint + */ + function unwrapToBTRFLY( uint _amount ) external returns ( uint ) { + _burn( msg.sender, _amount ); + + uint value = xBTRFLYValue( _amount ); + IERC20( xBTRFLY ).approve( staking, value ); // unstake sBTRFLY for BTRFLY + IStaking( staking ).unstake( value, false); + + IERC20( BTRFLY ).transfer( msg.sender, value ); + return value; + } + + /** + @notice wrap sBTRFLY + @param _amount uint + @return uint + */ + function wrapFromxBTRFLY( uint _amount ) external returns ( uint ) { + IERC20( xBTRFLY ).transferFrom( msg.sender, address(this), _amount ); + + uint value = wBTRFLYValue( _amount ); + _mint( msg.sender, value ); + return value; + } + + /** + @notice unwrap sBTRFLY + @param _amount uint + @return uint + */ + function unwrapToxBTRFLY( uint _amount ) external returns ( uint ) { + _burn( msg.sender, _amount ); + + uint value = xBTRFLYValue( _amount ); + IERC20( xBTRFLY ).transfer( msg.sender, value ); + return value; + } + + /** + @notice converts wBTRFLY amount to sBTRFLY + @param _amount uint + @return uint + */ + function xBTRFLYValue( uint _amount ) public view returns ( uint ) { + return _amount.mul( realIndex() ).div( 10 ** decimals() ); + } + + /** + @notice converts sBTRFLY amount to wBTRFLY + @param _amount uint + @return uint + */ + function wBTRFLYValue( uint _amount ) public view returns ( uint ) { + return _amount.mul( 10 ** decimals() ).div( realIndex() ); + } + + function realIndex() public view returns ( uint ) { + return IxBTRFLY(xBTRFLY).balanceForGons(realINDEX); + } + +} \ No newline at end of file diff --git a/semgrep-rules/security/redacted-cartel-custom-approval-bug.yaml b/semgrep-rules/security/redacted-cartel-custom-approval-bug.yaml new file mode 100644 index 0000000..74a752d --- /dev/null +++ b/semgrep-rules/security/redacted-cartel-custom-approval-bug.yaml @@ -0,0 +1,27 @@ +rules: + - + id: redacted-cartel-custom-approval-bug + message: transferFrom() can steal allowance of other accounts + metadata: + category: security + technology: + - solidity + cwe: "CWE-688: Function Call With Incorrect Variable or Reference as Argument" + confidence: HIGH + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://medium.com/immunefi/redacted-cartel-custom-approval-logic-bugfix-review-9b2d039ca2c5 + - https://etherscan.io/address/0x186E55C0BebD2f69348d94C4A27556d93C5Bd36C + patterns: + - pattern-inside: | + function transferFrom(...) { + ... + } + - pattern: _approve(..., allowance(sender, recipient).sub(amount, ...), ...); + languages: + - solidity + severity: ERROR + diff --git a/semgrep-rules/security/rigoblock-missing-access-control.sol b/semgrep-rules/security/rigoblock-missing-access-control.sol new file mode 100644 index 0000000..6f84382 --- /dev/null +++ b/semgrep-rules/security/rigoblock-missing-access-control.sol @@ -0,0 +1,1222 @@ +/** + *Submitted for verification at Etherscan.io on 2019-08-21 +*/ + +/* + + Copyright 2017-2018 RigoBlock, Rigo Investment Sagl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.25; +pragma experimental ABIEncoderV2; + +interface Authority { + + /* + * EVENTS + */ + event AuthoritySet(address indexed authority); + event WhitelisterSet(address indexed whitelister); + event WhitelistedUser(address indexed target, bool approved); + event WhitelistedRegistry(address indexed registry, bool approved); + event WhitelistedFactory(address indexed factory, bool approved); + event WhitelistedVault(address indexed vault, bool approved); + event WhitelistedDrago(address indexed drago, bool isWhitelisted); + event NewDragoEventful(address indexed dragoEventful); + event NewVaultEventful(address indexed vaultEventful); + event NewNavVerifier(address indexed navVerifier); + event NewExchangesAuthority(address indexed exchangesAuthority); + + /* + * CORE FUNCTIONS + */ + function setAuthority(address _authority, bool _isWhitelisted) external; + function setWhitelister(address _whitelister, bool _isWhitelisted) external; + function whitelistUser(address _target, bool _isWhitelisted) external; + function whitelistDrago(address _drago, bool _isWhitelisted) external; + function whitelistVault(address _vault, bool _isWhitelisted) external; + function whitelistRegistry(address _registry, bool _isWhitelisted) external; + function whitelistFactory(address _factory, bool _isWhitelisted) external; + function setDragoEventful(address _dragoEventful) external; + function setVaultEventful(address _vaultEventful) external; + function setNavVerifier(address _navVerifier) external; + function setExchangesAuthority(address _exchangesAuthority) external; + + /* + * CONSTANT PUBLIC FUNCTIONS + */ + function isWhitelistedUser(address _target) external view returns (bool); + function isAuthority(address _authority) external view returns (bool); + function isWhitelistedRegistry(address _registry) external view returns (bool); + function isWhitelistedDrago(address _drago) external view returns (bool); + function isWhitelistedVault(address _vault) external view returns (bool); + function isWhitelistedFactory(address _factory) external view returns (bool); + function getDragoEventful() external view returns (address); + function getVaultEventful() external view returns (address); + function getNavVerifier() external view returns (address); + function getExchangesAuthority() external view returns (address); +} + +interface ExchangesAuthority { + + /* + * EVENTS + */ + event AuthoritySet(address indexed authority); + event WhitelisterSet(address indexed whitelister); + event WhitelistedAsset(address indexed asset, bool approved); + event WhitelistedExchange(address indexed exchange, bool approved); + event WhitelistedWrapper(address indexed wrapper, bool approved); + event WhitelistedProxy(address indexed proxy, bool approved); + event WhitelistedMethod(bytes4 indexed method, address indexed exchange, bool approved); + event NewSigVerifier(address indexed sigVerifier); + event NewExchangeEventful(address indexed exchangeEventful); + event NewCasper(address indexed casper); + + /* + * CORE FUNCTIONS + */ + /// @dev Allows the owner to whitelist an authority + /// @param _authority Address of the authority + /// @param _isWhitelisted Bool whitelisted + function setAuthority(address _authority, bool _isWhitelisted) + external; + + /// @dev Allows the owner to whitelist a whitelister + /// @param _whitelister Address of the whitelister + /// @param _isWhitelisted Bool whitelisted + function setWhitelister(address _whitelister, bool _isWhitelisted) + external; + + /// @dev Allows a whitelister to whitelist an asset + /// @param _asset Address of the token + /// @param _isWhitelisted Bool whitelisted + function whitelistAsset(address _asset, bool _isWhitelisted) + external; + + /// @dev Allows a whitelister to whitelist an exchange + /// @param _exchange Address of the target exchange + /// @param _isWhitelisted Bool whitelisted + function whitelistExchange(address _exchange, bool _isWhitelisted) + external; + + /// @dev Allows a whitelister to whitelist an token wrapper + /// @param _wrapper Address of the target token wrapper + /// @param _isWhitelisted Bool whitelisted + function whitelistWrapper(address _wrapper, bool _isWhitelisted) + external; + + /// @dev Allows a whitelister to whitelist a tokenTransferProxy + /// @param _tokenTransferProxy Address of the proxy + /// @param _isWhitelisted Bool whitelisted + function whitelistTokenTransferProxy( + address _tokenTransferProxy, bool _isWhitelisted) + external; + + /// @dev Allows a whitelister to enable trading on a particular exchange + /// @param _asset Address of the token + /// @param _exchange Address of the exchange + /// @param _isWhitelisted Bool whitelisted + function whitelistAssetOnExchange( + address _asset, + address _exchange, + bool _isWhitelisted) + external; + + /// @dev Allows a whitelister to enable assiciate wrappers to a token + /// @param _token Address of the token + /// @param _wrapper Address of the exchange + /// @param _isWhitelisted Bool whitelisted + function whitelistTokenOnWrapper( + address _token, + address _wrapper, + bool _isWhitelisted) + external; + + /// @dev Allows an admin to whitelist a factory + /// @param _method Hex of the function ABI + /// @param _isWhitelisted Bool whitelisted + function whitelistMethod( + bytes4 _method, + address _adapter, + bool _isWhitelisted) + external; + + /// @dev Allows the owner to set the signature verifier + /// @param _sigVerifier Address of the logs contract + function setSignatureVerifier(address _sigVerifier) + external; + + /// @dev Allows the owner to set the exchange eventful + /// @param _exchangeEventful Address of the exchange logs contract + function setExchangeEventful(address _exchangeEventful) + external; + + /// @dev Allows the owner to associate an exchange to its adapter + /// @param _exchange Address of the exchange + /// @param _adapter Address of the adapter + function setExchangeAdapter(address _exchange, address _adapter) + external; + + /// @dev Allows the owner to set the casper contract + /// @param _casper Address of the casper contract + function setCasper(address _casper) + external; + + /* + * CONSTANT PUBLIC FUNCTIONS + */ + /// @dev Provides whether an address is an authority + /// @param _authority Address of the target authority + /// @return Bool is whitelisted + function isAuthority(address _authority) + external view + returns (bool); + + /// @dev Provides whether an asset is whitelisted + /// @param _asset Address of the target asset + /// @return Bool is whitelisted + function isWhitelistedAsset(address _asset) + external view + returns (bool); + + /// @dev Provides whether an exchange is whitelisted + /// @param _exchange Address of the target exchange + /// @return Bool is whitelisted + function isWhitelistedExchange(address _exchange) + external view + returns (bool); + + /// @dev Provides whether a token wrapper is whitelisted + /// @param _wrapper Address of the target exchange + /// @return Bool is whitelisted + function isWhitelistedWrapper(address _wrapper) + external view + returns (bool); + + /// @dev Provides whether a proxy is whitelisted + /// @param _tokenTransferProxy Address of the proxy + /// @return Bool is whitelisted + function isWhitelistedProxy(address _tokenTransferProxy) + external view + returns (bool); + + /// @dev Provides the address of the exchange adapter + /// @param _exchange Address of the exchange + /// @return Address of the adapter + function getExchangeAdapter(address _exchange) + external view + returns (address); + + /// @dev Provides the address of the signature verifier + /// @return Address of the verifier + function getSigVerifier() + external view + returns (address); + + /// @dev Checkes whether a token is allowed on an exchange + /// @param _token Address of the token + /// @param _exchange Address of the exchange + /// @return Bool the token is whitelisted on the exchange + function canTradeTokenOnExchange(address _token, address _exchange) + external view + returns (bool); + + /// @dev Checkes whether a token is allowed on a wrapper + /// @param _token Address of the token + /// @return Bool the token is whitelisted on the exchange + function canWrapTokenOnWrapper(address _token, address _wrapper) + external view + returns (bool); + + /// @dev Checkes whether a method is allowed on an exchange + function isMethodAllowed(bytes4 _method, address _exchange) + external view + returns (bool); + + /// @dev Checkes whether casper has been inizialized + /// @return Bool the casper contract has been initialized + function isCasperInitialized() + external view + returns (bool); + + /// @dev Provides the address of the casper contract + /// @return Address of the casper contract + function getCasper() + external view + returns (address); +} + +interface SigVerifier { + + /// @dev Verifies that a signature is valid. + /// @param hash Message hash that is signed. + /// @param signature Proof of signing. + /// @return Validity of order signature. + function isValidSignature( + bytes32 hash, + bytes signature + ) + external + view + returns (bool isValid); +} + +interface NavVerifier { + + /// @dev Verifies that a signature is valid. + /// @param sellPrice Price in wei + /// @param buyPrice Price in wei + /// @param signaturevaliduntilBlock Number of blocks till price expiry + /// @param hash Message hash that is signed. + /// @param signedData Proof of nav validity. + /// @notice mock function which returns true + function isValidNav( + uint256 sellPrice, + uint256 buyPrice, + uint256 signaturevaliduntilBlock, + bytes32 hash, + bytes signedData) + external + view + returns (bool isValid); +} + +interface Kyc + +{ + function isWhitelistedUser(address hodler) external view returns (bool); +} + +interface DragoEventful { + + /* + * EVENTS + */ + event BuyDrago(address indexed drago, address indexed from, address indexed to, uint256 amount, uint256 revenue, bytes name, bytes symbol); + event SellDrago(address indexed drago, address indexed from, address indexed to, uint256 amount, uint256 revenue, bytes name, bytes symbol); + event NewRatio(address indexed drago, address indexed from, uint256 newRatio); + event NewNAV(address indexed drago, address indexed from, address indexed to, uint256 sellPrice, uint256 buyPrice); + event NewFee(address indexed drago, address indexed group, address indexed who, uint256 transactionFee); + event NewCollector( address indexed drago, address indexed group, address indexed who, address feeCollector); + event DragoDao(address indexed drago, address indexed from, address indexed to, address dragoDao); + event DepositExchange(address indexed drago, address indexed exchange, address indexed token, uint256 value, uint256 amount); + event WithdrawExchange(address indexed drago, address indexed exchange, address indexed token, uint256 value, uint256 amount); + event OrderExchange(address indexed drago, address indexed exchange, address indexed cfd, uint256 value, uint256 revenue); + event TradeExchange(address indexed drago, address indexed exchange, address tokenGet, address tokenGive, uint256 amountGet, uint256 amountGive, address get); + event CancelOrder(address indexed drago, address indexed exchange, address indexed cfd, uint256 value, uint256 id); + event DealFinalized(address indexed drago, address indexed exchange, address indexed cfd, uint256 value, uint256 id); + event CustomDragoLog(bytes4 indexed methodHash, bytes encodedParams); + event CustomDragoLog2(bytes4 indexed methodHash, bytes32 topic2, bytes32 topic3, bytes encodedParams); + event DragoCreated(address indexed drago, address indexed group, address indexed owner, uint256 dragoId, string name, string symbol); + + /* + * CORE FUNCTIONS + */ + function buyDrago(address _who, address _targetDrago, uint256 _value, uint256 _amount, bytes _name, bytes _symbol) external returns (bool success); + function sellDrago(address _who, address _targetDrago, uint256 _amount, uint256 _revenue, bytes _name, bytes _symbol) external returns(bool success); + function changeRatio(address _who, address _targetDrago, uint256 _ratio) external returns(bool success); + function changeFeeCollector(address _who, address _targetDrago, address _feeCollector) external returns(bool success); + function changeDragoDao(address _who, address _targetDrago, address _dragoDao) external returns(bool success); + function setDragoPrice(address _who, address _targetDrago, uint256 _sellPrice, uint256 _buyPrice) external returns(bool success); + function setTransactionFee(address _who, address _targetDrago, uint256 _transactionFee) external returns(bool success); + function depositToExchange(address _who, address _targetDrago, address _exchange, address _token, uint256 _value) external returns(bool success); + function withdrawFromExchange(address _who, address _targetDrago, address _exchange, address _token, uint256 _value) external returns(bool success); + function customDragoLog(bytes4 _methodHash, bytes _encodedParams) external returns (bool success); + function customDragoLog2(bytes4 _methodHash, bytes32 topic2, bytes32 topic3, bytes _encodedParams) external returns (bool success); + function customExchangeLog(bytes4 _methodHash, bytes _encodedParams) external returns (bool success); + function customExchangeLog2(bytes4 _methodHash, bytes32 topic2, bytes32 topic3,bytes _encodedParams) external returns (bool success); + function createDrago(address _who, address _newDrago, string _name, string _symbol, uint256 _dragoId) external returns(bool success); +} + +interface Token { + + event Transfer(address indexed _from, address indexed _to, uint256 _value); + event Approval(address indexed _owner, address indexed _spender, uint256 _value); + + function transfer(address _to, uint256 _value) external returns (bool success); + function transferFrom(address _from, address _to, uint256 _value) external returns (bool success); + function approve(address _spender, uint256 _value) external returns (bool success); + + function balanceOf(address _who) external view returns (uint256); + function allowance(address _owner, address _spender) external view returns (uint256); +} + +contract ReentrancyGuard { + + // Locked state of mutex + bool private locked = false; + + /// @dev Functions with this modifer cannot be reentered. The mutex will be locked + /// before function execution and unlocked after. + modifier nonReentrant() { + // Ensure mutex is unlocked + require( + !locked, + "REENTRANCY_ILLEGAL" + ); + + // Lock mutex before function call + locked = true; + + // Perform function call + _; + + // Unlock mutex after function call + locked = false; + } +} + +contract Owned { + + address public owner; + + event NewOwner(address indexed old, address indexed current); + + modifier onlyOwner { + require(msg.sender == owner); + _; + } + + function setOwner(address _new) public onlyOwner { + require(_new != address(0)); + owner = _new; + emit NewOwner(owner, _new); + } +} + +contract SafeMath { + + function safeMul(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a * b; + assert(a == 0 || c / a == b); + return c; + } + + function safeDiv(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b > 0); + uint256 c = a / b; + assert(a == b * c + a % b); + return c; + } + + function safeSub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + function safeAdd(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c>=a && c>=b); + return c; + } +} + +library LibFindMethod { + + /// @dev Returns the method of an ABIencoded call + /// @param assembledData Bytes of the call data + /// @return Bytes4 of the function signature + function findMethod(bytes assembledData) + internal + pure + returns (bytes4 method) + { + // find the bytes4(keccak256('functionABI')) of the function + assembly { + // Load free memory pointer + method := mload(0x00) + let transaction := assembledData + method := mload(add(transaction, 0x20)) + } + return method; + } +} + +/// @title Drago - A set of rules for a drago. +/// @author Gabriele Rigo - +// solhint-disable-next-line +contract Drago is Owned, SafeMath, ReentrancyGuard { + + using LibFindMethod for *; + + string constant VERSION = 'HF 0.5.2'; + uint256 constant BASE = 1000000; // tokens are divisible by 1 million + + mapping (address => Account) accounts; + + DragoData data; + Admin admin; + + struct Receipt { + uint256 units; + uint32 activation; + } + + struct Account { + uint256 balance; + Receipt receipt; + mapping(address => address[]) approvedAccount; + } + + struct Transaction { + bytes assembledData; + } + + struct DragoData { + string name; + string symbol; + uint256 dragoId; + uint256 totalSupply; + uint256 sellPrice; + uint256 buyPrice; + uint256 transactionFee; // in basis points 1 = 0.01% + uint32 minPeriod; + } + + struct Admin { + address authority; + address dragoDao; + address feeCollector; + address kycProvider; + bool kycEnforced; + uint256 minOrder; // minimum stake to avoid dust clogging things up + uint256 ratio; // ratio is 80% + } + + modifier onlyDragoDao() { + require(msg.sender == admin.dragoDao); + _; + } + + modifier onlyOwnerOrAuthority() { + Authority auth = Authority(admin.authority); + require(auth.isAuthority(msg.sender) || msg.sender == owner); + _; + } + + modifier whenApprovedExchangeOrWrapper(address _target) { + bool approvedExchange = ExchangesAuthority(getExchangesAuthority()) + .isWhitelistedExchange(_target); + bool approvedWrapper = ExchangesAuthority(getExchangesAuthority()) + .isWhitelistedWrapper(_target); + require(approvedWrapper || approvedExchange); + _; + } + + modifier whenApprovedProxy(address _proxy) { + bool approved = ExchangesAuthority(getExchangesAuthority()) + .isWhitelistedProxy(_proxy); + require(approved); + _; + } + + modifier minimumStake(uint256 amount) { + require (amount >= admin.minOrder); + _; + } + + modifier hasEnough(uint256 _amount) { + require(accounts[msg.sender].balance >= _amount); + _; + } + + modifier positiveAmount(uint256 _amount) { + require(accounts[msg.sender].balance + _amount > accounts[msg.sender].balance); + _; + } + + modifier minimumPeriodPast() { + require(block.timestamp >= accounts[msg.sender].receipt.activation); + _; + } + + modifier buyPriceHigherOrEqual(uint256 _sellPrice, uint256 _buyPrice) { + require(_sellPrice <= _buyPrice); + _; + } + + modifier notPriceError(uint256 _sellPrice, uint256 _buyPrice) { + if (_sellPrice <= data.sellPrice / 10 || _buyPrice >= data.buyPrice * 10) return; + _; + } + + constructor( + string _dragoName, + string _dragoSymbol, + uint256 _dragoId, + address _owner, + address _authority) + public + { + data.name = _dragoName; + data.symbol = _dragoSymbol; + data.dragoId = _dragoId; + data.sellPrice = 1 ether; + data.buyPrice = 1 ether; + owner = _owner; + admin.authority = _authority; + admin.dragoDao = msg.sender; + admin.minOrder = 1 finney; + admin.feeCollector = _owner; + admin.ratio = 80; + } + + /* + * CORE FUNCTIONS + */ + /// @dev Allows Ether to be received. + /// @notice Used for settlements and withdrawals. + function() + external + payable + { + require(msg.value != 0); + } + + /// @dev Allows a user to buy into a drago. + /// @return Bool the function executed correctly. + function buyDrago() + external + payable + minimumStake(msg.value) + returns (bool success) + { + require(buyDragoInternal(msg.sender)); + return true; + } + + /// @dev Allows a user to buy into a drago on behalf of an address. + /// @param _hodler Address of the target user. + /// @return Bool the function executed correctly. + function buyDragoOnBehalf(address _hodler) + external + payable + minimumStake(msg.value) + returns (bool success) + { + require(buyDragoInternal(_hodler)); + return true; + } + + /// @dev Allows a user to sell from a drago. + /// @param _amount Number of shares to sell. + /// @return Bool the function executed correctly. + function sellDrago(uint256 _amount) + external + nonReentrant + hasEnough(_amount) + positiveAmount(_amount) + minimumPeriodPast + returns (bool success) + { + uint256 feeDrago; + uint256 feeDragoDao; + uint256 netAmount; + uint256 netRevenue; + (feeDrago, feeDragoDao, netAmount, netRevenue) = getSaleAmounts(_amount); + addSaleLog(_amount, netRevenue); + allocateSaleTokens(msg.sender, _amount, feeDrago, feeDragoDao); + data.totalSupply = safeSub(data.totalSupply, netAmount); + msg.sender.transfer(netRevenue); + return true; + } + + /// @dev Allows drago owner or authority to set the price for a drago. + /// @param _newSellPrice Price in wei. + /// @param _newBuyPrice Price in wei. + /// @param _signaturevaliduntilBlock Number of blocks till expiry of new data. + /// @param _hash Bytes32 of the transaction hash. + /// @param _signedData Bytes of extradata and signature. + function setPrices( + uint256 _newSellPrice, + uint256 _newBuyPrice, + uint256 _signaturevaliduntilBlock, + bytes32 _hash, + bytes _signedData) + external + nonReentrant + onlyOwnerOrAuthority + buyPriceHigherOrEqual(_newSellPrice, _newBuyPrice) + notPriceError(_newSellPrice, _newBuyPrice) + { + require( + isValidNav( + _newSellPrice, + _newBuyPrice, + _signaturevaliduntilBlock, + _hash, + _signedData + ) + ); + DragoEventful events = DragoEventful(getDragoEventful()); + require(events.setDragoPrice(msg.sender, this, _newSellPrice, _newBuyPrice)); + data.sellPrice = _newSellPrice; + data.buyPrice = _newBuyPrice; + } + + /// @dev Allows drago dao/factory to change fee split ratio. + /// @param _ratio Number of ratio for wizard, from 0 to 100. + function changeRatio(uint256 _ratio) + external + onlyDragoDao + { + DragoEventful events = DragoEventful(getDragoEventful()); + require(events.changeRatio(msg.sender, this, _ratio)); + admin.ratio = _ratio; + } + + /// @dev Allows drago owner to set the transaction fee. + /// @param _transactionFee Value of the transaction fee in basis points. + function setTransactionFee(uint256 _transactionFee) + external + onlyOwner + { + require(_transactionFee <= 100); //fee cannot be higher than 1% + DragoEventful events = DragoEventful(getDragoEventful()); + require(events.setTransactionFee(msg.sender, this, _transactionFee)); + data.transactionFee = _transactionFee; + } + + /// @dev Allows owner to decide where to receive the fee. + /// @param _feeCollector Address of the fee receiver. + function changeFeeCollector(address _feeCollector) + external + onlyOwner + { + DragoEventful events = DragoEventful(getDragoEventful()); + events.changeFeeCollector(msg.sender, this, _feeCollector); + admin.feeCollector = _feeCollector; + } + + /// @dev Allows drago dao/factory to upgrade its address. + /// @param _dragoDao Address of the new drago dao. + function changeDragoDao(address _dragoDao) + external + onlyDragoDao + { + DragoEventful events = DragoEventful(getDragoEventful()); + require(events.changeDragoDao(msg.sender, this, _dragoDao)); + admin.dragoDao = _dragoDao; + } + + /// @dev Allows drago dao/factory to change the minimum holding period. + /// @param _minPeriod Time in seconds. + function changeMinPeriod(uint32 _minPeriod) + external + onlyDragoDao + { + data.minPeriod = _minPeriod; + } + + function enforceKyc( + bool _enforced, + address _kycProvider) + external + onlyOwner + { + admin.kycEnforced = _enforced; + admin.kycProvider = _kycProvider; + } + + /// @dev Allows owner to set an allowance to an approved token transfer proxy. + /// @param _tokenTransferProxy Address of the proxy to be approved. + /// @param _token Address of the token to receive allowance for. + /// @param _amount Number of tokens approved for spending. + function setAllowance( + address _tokenTransferProxy, + address _token, + uint256 _amount) + external + onlyOwner + whenApprovedProxy(_tokenTransferProxy) + { + require(setAllowancesInternal(_tokenTransferProxy, _token, _amount)); + } + + /// @dev Allows owner to set allowances to multiple approved tokens with one call. + /// @param _tokenTransferProxy Address of the proxy to be approved. + /// @param _tokens Address of the token to receive allowance for. + /// @param _amounts Array of number of tokens to be approved. + // ruleid: rigoblock-missing-access-control + function setMultipleAllowances( + address _tokenTransferProxy, + address[] _tokens, + uint256[] _amounts) + external + { + for (uint256 i = 0; i < _tokens.length; i++) { + if (!setAllowancesInternal(_tokenTransferProxy, _tokens[i], _amounts[i])) continue; + } + } + + /// @dev Allows owner to operate on exchange through extension. + /// @param _exchange Address of the target exchange. + /// @param transaction ABIencoded transaction. + function operateOnExchange( + address _exchange, + Transaction memory transaction) + public + onlyOwner + nonReentrant + whenApprovedExchangeOrWrapper(_exchange) + returns (bool success) + { + address adapter = getExchangeAdapter(_exchange); + bytes memory transactionData = transaction.assembledData; + require( + methodAllowedOnExchange( + findMethod(transactionData), + adapter + ) + ); + + bytes memory response; + bool failed = true; + + assembly { + + let succeeded := delegatecall( + sub(gas, 5000), + adapter, + add(transactionData, 0x20), + mload(transactionData), + 0, + 32) // 0x0 + + // load delegatecall output + response := mload(0) + failed := iszero(succeeded) + + switch failed + case 1 { + // throw if delegatecall failed + revert(0, 0) + } + } + + return (success = true); + } + + /// @dev Allows owner or approved exchange to send a transaction to exchange + /// @dev With data of signed/unsigned transaction + /// @param _exchange Address of the exchange + /// @param transactions Array of ABI encoded transactions + function batchOperateOnExchange( + address _exchange, + Transaction[] memory transactions) + public + onlyOwner + nonReentrant + whenApprovedExchangeOrWrapper(_exchange) + { + for (uint256 i = 0; i < transactions.length; i++) { + if (!operateOnExchange(_exchange, transactions[i])) continue; + } + } + + /* + * CONSTANT PUBLIC FUNCTIONS + */ + /// @dev Calculates how many shares a user holds. + /// @param _who Address of the target account. + /// @return Number of shares. + function balanceOf(address _who) + external + view + returns (uint256) + { + return accounts[_who].balance; + } + + /// @dev Gets the address of the logger contract. + /// @return Address of the logger contrac. + function getEventful() + external + view + returns (address) + { + Authority auth = Authority(admin.authority); + return auth.getDragoEventful(); + } + + /// @dev Finds details of a drago pool. + /// @return String name of a drago. + /// @return String symbol of a drago. + /// @return Value of the share price in wei. + /// @return Value of the share price in wei. + function getData() + external + view + returns ( + string name, + string symbol, + uint256 sellPrice, + uint256 buyPrice + ) + { + name = data.name; + symbol = data.symbol; + sellPrice = data.sellPrice; + buyPrice = data.buyPrice; + } + + /// @dev Returns the price of a pool. + /// @return Value of the share price in wei. + function calcSharePrice() + external + view + returns (uint256) + { + return data.sellPrice; + } + + /// @dev Finds the administrative data of the pool. + /// @return Address of the account where a user collects fees. + /// @return Address of the drago dao/factory. + /// @return Number of the fee split ratio. + /// @return Value of the transaction fee in basis points. + /// @return Number of the minimum holding period for shares. + function getAdminData() + external + view + returns ( + address, //owner + address feeCollector, + address dragoDao, + uint256 ratio, + uint256 transactionFee, + uint32 minPeriod + ) + { + return ( + owner, + admin.feeCollector, + admin.dragoDao, + admin.ratio, + data.transactionFee, + data.minPeriod + ); + } + + function getKycProvider() + external + view + returns (address) + { + if(admin.kycEnforced) { + return admin.kycProvider; + } + } + + /// @dev Verifies that a signature is valid. + /// @param hash Message hash that is signed. + /// @param signature Proof of signing. + /// @return Validity of order signature. + function isValidSignature( + bytes32 hash, + bytes signature + ) + external + view + returns (bool isValid) + { + isValid = SigVerifier(getSigVerifier()) + .isValidSignature(hash, signature); + return isValid; + } + + /// @dev Finds the exchanges authority. + /// @return Address of the exchanges authority. + function getExchangesAuth() + external + view + returns (address) + { + return getExchangesAuthority(); + } + + /// @dev Returns the total amount of issued tokens for this drago. + /// @return Number of shares. + function totalSupply() + external view + returns (uint256) + { + return data.totalSupply; + } + + /* + * INTERNAL FUNCTIONS + */ + + /// @dev Executes the pool purchase. + /// @param _hodler Address of the target user. + /// @return Bool the function executed correctly. + function buyDragoInternal(address _hodler) + internal + returns (bool success) + { + if (admin.kycProvider != 0x0) { + require(Kyc(admin.kycProvider).isWhitelistedUser(_hodler)); + } + uint256 grossAmount; + uint256 feeDrago; + uint256 feeDragoDao; + uint256 amount; + (grossAmount, feeDrago, feeDragoDao, amount) = getPurchaseAmounts(); + addPurchaseLog(amount); + allocatePurchaseTokens(_hodler, amount, feeDrago, feeDragoDao); + data.totalSupply = safeAdd(data.totalSupply, grossAmount); + return true; + } + + /// @dev Allocates tokens to buyer, splits fee in tokens to wizard and dao. + /// @param _hodler Address of the buyer. + /// @param _amount Value of issued tokens. + /// @param _feeDrago Number of shares as fee. + /// @param _feeDragoDao Number of shares as fee to dao. + function allocatePurchaseTokens( + address _hodler, + uint256 _amount, + uint256 _feeDrago, + uint256 _feeDragoDao) + internal + { + accounts[_hodler].balance = safeAdd(accounts[_hodler].balance, _amount); + accounts[admin.feeCollector].balance = safeAdd(accounts[admin.feeCollector].balance, _feeDrago); + accounts[admin.dragoDao].balance = safeAdd(accounts[admin.dragoDao].balance, _feeDragoDao); + accounts[_hodler].receipt.activation = uint32(now) + data.minPeriod; + } + + /// @dev Destroys tokens of seller, splits fee in tokens to wizard and dao. + /// @param _hodler Address of the seller. + /// @param _amount Value of burnt tokens. + /// @param _feeDrago Number of shares as fee. + /// @param _feeDragoDao Number of shares as fee to dao. + function allocateSaleTokens( + address _hodler, + uint256 _amount, + uint256 _feeDrago, + uint256 _feeDragoDao) + internal + { + accounts[_hodler].balance = safeSub(accounts[_hodler].balance, _amount); + accounts[admin.feeCollector].balance = safeAdd(accounts[admin.feeCollector].balance, _feeDrago); + accounts[admin.dragoDao].balance = safeAdd(accounts[admin.dragoDao].balance, _feeDragoDao); + } + + /// @dev Sends a buy log to the eventful contract. + /// @param _amount Number of purchased shares. + function addPurchaseLog(uint256 _amount) + internal + { + bytes memory name = bytes(data.name); + bytes memory symbol = bytes(data.symbol); + Authority auth = Authority(admin.authority); + DragoEventful events = DragoEventful(auth.getDragoEventful()); + require(events.buyDrago(msg.sender, this, msg.value, _amount, name, symbol)); + } + + /// @dev Sends a sell log to the eventful contract. + /// @param _amount Number of sold shares. + /// @param _netRevenue Value of sale for hodler. + function addSaleLog(uint256 _amount, uint256 _netRevenue) + internal + { + bytes memory name = bytes(data.name); + bytes memory symbol = bytes(data.symbol); + Authority auth = Authority(admin.authority); + DragoEventful events = DragoEventful(auth.getDragoEventful()); + require(events.sellDrago(msg.sender, this, _amount, _netRevenue, name, symbol)); + } + + /// @dev Allows owner to set an infinite allowance to an approved exchange. + /// @param _tokenTransferProxy Address of the proxy to be approved. + /// @param _token Address of the token to receive allowance for. + function setAllowancesInternal( + address _tokenTransferProxy, + address _token, + uint256 _amount) + internal + returns (bool) + { + require(Token(_token) + .approve(_tokenTransferProxy, _amount)); + return true; + } + + /// @dev Calculates the correct purchase amounts. + /// @return Number of new shares. + /// @return Value of fee in shares. + /// @return Value of fee in shares to dao. + /// @return Value of net purchased shares. + function getPurchaseAmounts() + internal + view + returns ( + uint256 grossAmount, + uint256 feeDrago, + uint256 feeDragoDao, + uint256 amount + ) + { + grossAmount = safeDiv(msg.value * BASE, data.buyPrice); + uint256 fee = safeMul(grossAmount, data.transactionFee) / 10000; //fee is in basis points + return ( + grossAmount, + feeDrago = safeMul(fee , admin.ratio) / 100, + feeDragoDao = safeSub(fee, feeDrago), + amount = safeSub(grossAmount, fee) + ); + } + + /// @dev Calculates the correct sale amounts. + /// @return Value of fee in shares. + /// @return Value of fee in shares to dao. + /// @return Value of net sold shares. + /// @return Value of sale amount for hodler. + function getSaleAmounts(uint256 _amount) + internal + view + returns ( + uint256 feeDrago, + uint256 feeDragoDao, + uint256 netAmount, + uint256 netRevenue + ) + { + uint256 fee = safeMul(_amount, data.transactionFee) / 10000; //fee is in basis points + return ( + feeDrago = safeMul(fee, admin.ratio) / 100, + feeDragoDao = safeSub(fee, feeDragoDao), + netAmount = safeSub(_amount, fee), + netRevenue = (safeMul(netAmount, data.sellPrice) / BASE) + ); + } + + /// @dev Gets the address of the logger contract. + /// @return Address of the logger contrac. + function getDragoEventful() + internal + view + returns (address) + { + Authority auth = Authority(admin.authority); + return auth.getDragoEventful(); + } + + /// @dev Returns the address of the signature verifier. + /// @return Address of the verifier contract. + function getSigVerifier() + internal + view + returns (address) + { + return ExchangesAuthority( + Authority(admin.authority) + .getExchangesAuthority()) + .getSigVerifier(); + } + + /// @dev Returns the address of the price verifier. + /// @return Address of the verifier contract. + function getNavVerifier() + internal + view + returns (address) + { + return Authority(admin.authority) + .getNavVerifier(); + } + + /// @dev Verifies that a signature is valid. + /// @param sellPrice Price in wei. + /// @param buyPrice Price in wei. + /// @param signaturevaliduntilBlock Number of blocks till price expiry. + /// @param hash Message hash that is signed. + /// @param signedData Proof of nav validity. + /// @return Bool validity of signed price update. + function isValidNav( + uint256 sellPrice, + uint256 buyPrice, + uint256 signaturevaliduntilBlock, + bytes32 hash, + bytes signedData) + internal + view + returns (bool isValid) + { + isValid = NavVerifier(getNavVerifier()).isValidNav( + sellPrice, + buyPrice, + signaturevaliduntilBlock, + hash, + signedData + ); + return isValid; + } + + /// @dev Finds the exchanges authority. + /// @return Address of the exchanges authority. + function getExchangesAuthority() + internal + view + returns (address) + { + return Authority(admin.authority).getExchangesAuthority(); + } + + /// @dev Returns the address of the exchange adapter. + /// @param _exchange Address of the target exchange. + /// @return Address of the exchange adapter. + function getExchangeAdapter(address _exchange) + internal + view + returns (address) + { + return ExchangesAuthority( + Authority(admin.authority) + .getExchangesAuthority()) + .getExchangeAdapter(_exchange); + } + + /// @dev Returns the method of a call. + /// @param assembledData Bytes of the encoded transaction. + /// @return Bytes4 function signature. + function findMethod(bytes assembledData) + internal + pure + returns (bytes4 method) + { + return method = LibFindMethod.findMethod(assembledData); + } + + /// @dev Finds if a method is allowed on an exchange. + /// @param _adapter Address of the target exchange. + /// @return Bool the method is allowed. + function methodAllowedOnExchange( + bytes4 _method, + address _adapter) + internal + view + returns (bool) + { + return ExchangesAuthority( + Authority(admin.authority) + .getExchangesAuthority()) + .isMethodAllowed(_method, _adapter); + } +} \ No newline at end of file diff --git a/semgrep-rules/security/rigoblock-missing-access-control.yaml b/semgrep-rules/security/rigoblock-missing-access-control.yaml new file mode 100644 index 0000000..7c6f40c --- /dev/null +++ b/semgrep-rules/security/rigoblock-missing-access-control.yaml @@ -0,0 +1,25 @@ +rules: + - + id: rigoblock-missing-access-control + message: setMultipleAllowances() is missing onlyOwner modifier + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: HIGH + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/danielvf/status/1494317265835147272 + - https://etherscan.io/address/0x876b9ebd725d1fa0b879fcee12560a6453b51dc8 + - https://play.secdim.com/game/dapp/challenge/rigoownsol + patterns: + - pattern: function setMultipleAllowances(...) {...} + - pattern-not: function setMultipleAllowances(...) onlyOwner {...} + languages: + - solidity + severity: ERROR + diff --git a/semgrep-rules/security/sense-missing-oracle-access-control.sol b/semgrep-rules/security/sense-missing-oracle-access-control.sol new file mode 100644 index 0000000..289320b --- /dev/null +++ b/semgrep-rules/security/sense-missing-oracle-access-control.sol @@ -0,0 +1,806 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +// External references +import { FixedPoint } from "@balancer-labs/v2-solidity-utils/contracts/math/FixedPoint.sol"; +import { Math as BasicMath } from "@balancer-labs/v2-solidity-utils/contracts/math/Math.sol"; +import { BalancerPoolToken } from "@balancer-labs/v2-pool-utils/contracts/BalancerPoolToken.sol"; +import { ERC20 } from "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/ERC20.sol"; +import { LogCompression } from "@balancer-labs/v2-solidity-utils/contracts/helpers/LogCompression.sol"; + +import { IMinimalSwapInfoPool } from "@balancer-labs/v2-vault/contracts/interfaces/IMinimalSwapInfoPool.sol"; +import { IVault } from "@balancer-labs/v2-vault/contracts/interfaces/IVault.sol"; +import { IERC20 } from "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/IERC20.sol"; + +import { Errors, _require } from "./Errors.sol"; +import { PoolPriceOracle } from "./oracle/PoolPriceOracle.sol"; + +interface AdapterLike { + function scale() external returns (uint256); + + function scaleStored() external view returns (uint256); + + function target() external view returns (address); + + function symbol() external view returns (string memory); + + function name() external view returns (string memory); + + function getUnderlyingPrice() external view returns (uint256); +} + +/* + SPACE + * '* + * + * + * + * + * + . . + . ; + : - --+- - + ! . ! + +*/ + +/// @notice A Yieldspace implementation extended such that LPs can deposit +/// [Principal Token, Yield-bearing asset], rather than [Principal Token, Underlying], while keeping the benefits of the +/// yieldspace invariant (e.g. it can hold [Principal Token, cDAI], rather than [Principal Token, DAI], while still operating +/// in "yield space" for the principal token side. See the YieldSpace paper for more https://yield.is/YieldSpace.pdf) +/// @dev We use much more internal storage here than in other Sense contracts because it +/// conforms to Balancer's own style, and we're using several Balancer functions that play nicer if we do. +/// @dev Requires an external "Adapter" contract with a `scale()` function which returns the +/// current exchange rate from Target to the Underlying asset. +contract Space is IMinimalSwapInfoPool, BalancerPoolToken, PoolPriceOracle { + using FixedPoint for uint256; + + /* ========== STRUCTURES ========== */ + + struct OracleData { + uint16 oracleIndex; + uint32 oracleSampleInitialTimestamp; + bool oracleEnabled; + int200 logInvariant; + } + + /* ========== CONSTANTS ========== */ + + /// @notice Minimum BPT we can have for this pool after initialization + uint256 public constant MINIMUM_BPT = 1e6; + + /* ========== PUBLIC IMMUTABLES ========== */ + + /// @notice Adapter address for the associated Series + address public immutable adapter; + + /// @notice Maturity timestamp for associated Series + uint256 public immutable maturity; + + /// @notice Principal Token index (there are only two tokens in this pool, so `targeti` is always just the complement) + uint256 public immutable pti; + + /// @notice Yieldspace config, passed in from the Space Factory + uint256 public immutable ts; + uint256 public immutable g1; + uint256 public immutable g2; + + /* ========== INTERNAL IMMUTABLES ========== */ + + /// @dev Balancer pool id (as registered with the Balancer Vault) + bytes32 internal immutable _poolId; + + /// @dev Token registered at index 0 for this pool + IERC20 internal immutable _token0; + + /// @dev Token registered at index one for this pool + IERC20 internal immutable _token1; + + /// @dev Factor needed to scale the PT to 18 decimals + uint256 internal immutable _scalingFactorPT; + + /// @dev Factor needed to scale the Target token to 18 decimals + uint256 internal immutable _scalingFactorTarget; + + /// @dev Balancer Vault + IVault internal immutable _vault; + + /// @dev Contract that collects Balancer protocol fees + address internal immutable _protocolFeesCollector; + + /* ========== INTERNAL MUTABLE STORAGE ========== */ + + /// @dev Scale value for the yield-bearing asset's first `join` (i.e. initialization) + uint256 internal _initScale; + + /// @dev Invariant tracking for calculating Balancer protocol fees + uint256 internal _lastToken0Reserve; + uint256 internal _lastToken1Reserve; + + /// @dev Oracle sample collection metadata + OracleData internal oracleData; + + constructor( + IVault vault, + address _adapter, + uint256 _maturity, + address pt, + uint256 _ts, + uint256 _g1, + uint256 _g2, + bool _oracleEnabled + ) BalancerPoolToken(AdapterLike(_adapter).name(), AdapterLike(_adapter).symbol()) { + bytes32 poolId = vault.registerPool(IVault.PoolSpecialization.TWO_TOKEN); + + address target = AdapterLike(_adapter).target(); + IERC20[] memory tokens = new IERC20[](2); + + // Ensure that the array of tokens is correctly ordered + uint256 _pti = pt < target ? 0 : 1; + tokens[_pti] = IERC20(pt); + tokens[1 - _pti] = IERC20(target); + vault.registerTokens(poolId, tokens, new address[](2)); + + // Set Balancer-specific pool config + _vault = vault; + _poolId = poolId; + _token0 = tokens[0]; + _token1 = tokens[1]; + _protocolFeesCollector = address(vault.getProtocolFeesCollector()); + + _scalingFactorPT = 10**(BasicMath.sub(uint256(18), ERC20(pt).decimals())); + _scalingFactorTarget = 10**(BasicMath.sub(uint256(18), ERC20(target).decimals())); + + // Set Yieldspace config + g1 = _g1; // Fees are baked into factors `g1` & `g2`, + g2 = _g2; // see the "Fees" section of the yieldspace paper + ts = _ts; + + // Set Space-specific slots + pti = _pti; + adapter = _adapter; + maturity = _maturity; + oracleData.oracleEnabled = _oracleEnabled; + } + + /* ========== BALANCER VAULT HOOKS ========== */ + + function onJoinPool( + bytes32 poolId, + address, /* sender */ + address recipient, + uint256[] memory reserves, + uint256 lastChangeBlock, + uint256 protocolSwapFeePercentage, + bytes memory userData + ) external override onlyVault(poolId) returns (uint256[] memory, uint256[] memory) { + // Space does not have multiple join types like other Balancer pools, + // instead, its `joinPool` always behaves like `EXACT_TOKENS_IN_FOR_BPT_OUT` + + _require(maturity >= block.timestamp, Errors.POOL_PAST_MATURITY); + + (uint256[] memory reqAmountsIn, uint256 minBptOut) = abi.decode(userData, (uint256[], uint256)); + + // Upscale both requested amounts and reserves to 18 decimals + _upscaleArray(reserves); + _upscaleArray(reqAmountsIn); + + if (totalSupply() == 0) { + uint256 initScale = AdapterLike(adapter).scale(); + + // Convert target balance into Underlying + // note We assume scale values will always be 18 decimals + uint256 underlyingIn = reqAmountsIn[1 - pti].mulDown(initScale); + + // Just like weighted pool 2 token from the balancer v2 monorepo, + // we lock MINIMUM_BPT in by minting it for the PT address. This reduces potential + // issues with rounding and ensures that this code path will only be executed once + _mintPoolTokens(address(0), MINIMUM_BPT); + + uint256 bptToMint = underlyingIn.sub(MINIMUM_BPT); + + // Mint the recipient BPT comensurate with the value of their join in Underlying + _mintPoolTokens(recipient, bptToMint); + + _require(bptToMint >= minBptOut, Errors.BPT_OUT_MIN_AMOUNT); + + // Amounts entering the Pool, so we round up + _downscaleUpArray(reqAmountsIn); + + // Set the scale value all future deposits will be backdated to + _initScale = initScale; + + // For the first join, we don't pull any PT, regardless of what the caller requested. + // This starts this pool off as synthetic Underlying only, as the yieldspace invariant expects + delete reqAmountsIn[pti]; + + // Cache starting Target reserves + reserves = reqAmountsIn; + + // Cache new reserves, post join + _cacheReserves(reserves); + + return (reqAmountsIn, new uint256[](2)); + } else { + // Update oracle with upscaled reserves + // ok: sense-missing-oracle-access-control + _updateOracle(lastChangeBlock, reserves[pti], reserves[1 - pti]); + + // Calculate fees due before updating bpt balances to determine invariant growth from just swap fees + if (protocolSwapFeePercentage != 0) { + // This doesn't break the YS virtual reserves efficiency trick because, even though we're minting new BPT, + // the BPT is still getting denser faster than it's getting diluted, + // meaning that it'll never fall below invariant #23 in the YS paper + _mintPoolTokens(_protocolFeesCollector, _bptFeeDue(reserves, protocolSwapFeePercentage)); + } + + (uint256 bptToMint, uint256[] memory amountsIn) = _tokensInForBptOut(reqAmountsIn, reserves); + + _require(bptToMint >= minBptOut, Errors.BPT_OUT_MIN_AMOUNT); + + // `recipient` receives liquidity tokens + _mintPoolTokens(recipient, bptToMint); + + // Update reserves for caching + // + // No risk of overflow as this function will only succeed if the user actually has `amountsIn` and + // the max token supply for a well-behaved token is bounded by `uint256 totalSupply` + reserves[0] += amountsIn[0]; + reserves[1] += amountsIn[1]; + + // Cache new reserves, post join + _cacheReserves(reserves); + + // Amounts entering the Pool, so we round up + _downscaleUpArray(amountsIn); + + // Inspired by PR #990 in the balancer v2 monorepo, we always return pt dueProtocolFeeAmounts + // to the Vault, and pay protocol fees by minting BPT directly to the protocolFeeCollector instead + return (amountsIn, new uint256[](2)); + } + } + + function onExitPool( + bytes32 poolId, + address sender, + address, /* recipient */ + uint256[] memory reserves, + uint256 lastChangeBlock, + uint256 protocolSwapFeePercentage, + bytes memory userData + ) external override onlyVault(poolId) returns (uint256[] memory, uint256[] memory) { + // Space does not have multiple exit types like other Balancer pools, + // instead, its `exitPool` always behaves like `EXACT_BPT_IN_FOR_TOKENS_OUT` + + // Upscale reserves to 18 decimals + _upscaleArray(reserves); + + // Update oracle with upscaled reserves + // ok: sense-missing-oracle-access-control + _updateOracle(lastChangeBlock, reserves[pti], reserves[1 - pti]); + + // Calculate fees due before updating bpt balances to determine invariant growth from just swap fees + if (protocolSwapFeePercentage != 0) { + _mintPoolTokens(_protocolFeesCollector, _bptFeeDue(reserves, protocolSwapFeePercentage)); + } + + // Determine what percentage of the pool the BPT being passed in represents + uint256 bptAmountIn = abi.decode(userData, (uint256)); + + // Calculate the amount of tokens owed in return for giving that amount of BPT in + uint256[] memory amountsOut = new uint256[](2); + uint256 _totalSupply = totalSupply(); + // Even though we are sending tokens to the user, we round both amounts out *up* here, b/c: + // 1) Maximizing the number of tokens users get when exiting maximizes the + // number of BPT we mint for users joining afterwards (it maximizes the equation + // totalSupply * amtIn / reserves). As a result, we ensure that the total supply component of the + // numerator is greater than the denominator in the "marginal rate equation" (eq. 2) from the YS paper + // 2) We lock MINIMUM_BPT away at initialization, which means a number of reserves will + // remain untouched and will function as a buffer for "off by one" rounding errors + amountsOut[0] = reserves[0].mulUp(bptAmountIn).divUp(_totalSupply); + amountsOut[1] = reserves[1].mulUp(bptAmountIn).divUp(_totalSupply); + + // `sender` pays for the liquidity + _burnPoolTokens(sender, bptAmountIn); + + // Update reserves for caching + reserves[0] = reserves[0].sub(amountsOut[0]); + reserves[1] = reserves[1].sub(amountsOut[1]); + + // Cache new invariant and reserves, post exit + _cacheReserves(reserves); + + // Amounts are leaving the Pool, so we round down + _downscaleDownArray(amountsOut); + + return (amountsOut, new uint256[](2)); + } + + function onSwap( + SwapRequest memory request, + uint256 reservesTokenIn, + uint256 reservesTokenOut + ) external override returns (uint256) { + bool pTIn = request.tokenIn == _token0 ? pti == 0 : pti == 1; + + uint256 scalingFactorTokenIn = _scalingFactor(pTIn); + uint256 scalingFactorTokenOut = _scalingFactor(!pTIn); + + // Upscale reserves to 18 decimals + reservesTokenIn = _upscale(reservesTokenIn, scalingFactorTokenIn); + reservesTokenOut = _upscale(reservesTokenOut, scalingFactorTokenOut); + + // Update oracle with upscaled reserves + // ruleid: sense-missing-oracle-access-control + _updateOracle( + request.lastChangeBlock, + pTIn ? reservesTokenIn : reservesTokenOut, + pTIn ? reservesTokenOut: reservesTokenIn + ); + + uint256 scale = AdapterLike(adapter).scale(); + + if (pTIn) { + // Add LP supply to PT reserves, as suggested by the yieldspace paper + reservesTokenIn = reservesTokenIn.add(totalSupply()); + + // Backdate the Target reserves and convert to Underlying, as if it were still t0 (initialization) + reservesTokenOut = reservesTokenOut.mulDown(_initScale); + } else { + // Backdate the Target reserves and convert to Underlying, as if it were still t0 (initialization) + reservesTokenIn = reservesTokenIn.mulDown(_initScale); + + // Add LP supply to PT reserves, as suggested by the yieldspace paper + reservesTokenOut = reservesTokenOut.add(totalSupply()); + } + + if (request.kind == IVault.SwapKind.GIVEN_IN) { + request.amount = _upscale(request.amount, scalingFactorTokenIn); + // If Target is being swapped in, convert the amountIn to Underlying using present day Scale + if (!pTIn) { + request.amount = request.amount.mulDown(scale); + } + + // Determine the amountOut + uint256 amountOut = _onSwap(pTIn, true, request.amount, reservesTokenIn, reservesTokenOut); + + // If PTs are being swapped in, convert the Underlying out back to Target using present day Scale + if (pTIn) { + amountOut = amountOut.divDown(scale); + } + + // AmountOut, so we round down + return _downscaleDown(amountOut, scalingFactorTokenOut); + } else { + request.amount = _upscale(request.amount, scalingFactorTokenOut); + // If PTs are being swapped in, convert the amountOut from Target to Underlying using present day Scale + if (pTIn) { + request.amount = request.amount.mulDown(scale); + } + + // Determine the amountIn + uint256 amountIn = _onSwap(pTIn, false, request.amount, reservesTokenIn, reservesTokenOut); + + // If Target is being swapped in, convert the amountIn back to Target using present day Scale + if (!pTIn) { + amountIn = amountIn.divDown(scale); + } + + // amountIn, so we round up + return _downscaleUp(amountIn, scalingFactorTokenIn); + } + } + + /* ========== INTERNAL JOIN/SWAP ACCOUNTING ========== */ + + /// @notice Calculate the max amount of BPT that can be minted from the requested amounts in, + // given the ratio of the reserves, and assuming we don't make any swaps + function _tokensInForBptOut(uint256[] memory reqAmountsIn, uint256[] memory reserves) + internal + view + returns (uint256, uint256[] memory) + { + // Disambiguate reserves wrt token type + (uint256 pTReserves, uint256 targetReserves) = (reserves[pti], reserves[1 - pti]); + + uint256[] memory amountsIn = new uint256[](2); + + // An empty PT reserve occurs after + // 1) Pool initialization + // 2) When the entire PT side is swapped out of the pool without implying a negative rate + if (pTReserves == 0) { + uint256 reqTargetIn = reqAmountsIn[1 - pti]; + // Mint LP shares according to the relative amount of Target being offered + uint256 bptToMint = reqTargetIn.mulDown(_initScale); + + // Pull the entire offered Target + amountsIn[1 - pti] = reqTargetIn; + + return (bptToMint, amountsIn); + } else { + // Disambiguate requested amounts wrt token type + (uint256 reqPTIn, uint256 reqTargetIn) = (reqAmountsIn[pti], reqAmountsIn[1 - pti]); + uint256 _totalSupply = totalSupply(); + // Caclulate the percentage of the pool we'd get if we pulled all of the requested Target in + uint256 bptToMintTarget = BasicMath.mul(_totalSupply, reqTargetIn) / targetReserves; + + // Caclulate the percentage of the pool we'd get if we pulled all of the requested PT in + uint256 bptToMintPT = BasicMath.mul(_totalSupply, reqPTIn) / pTReserves; + + // Determine which amountIn is our limiting factor + if (bptToMintTarget < bptToMintPT) { + amountsIn[pti] = BasicMath.mul(pTReserves, reqTargetIn) / targetReserves; + amountsIn[1 - pti] = reqTargetIn; + + return (bptToMintTarget, amountsIn); + } else { + amountsIn[pti] = reqPTIn; + amountsIn[1 - pti] = BasicMath.mul(targetReserves, reqPTIn) / pTReserves; + + return (bptToMintPT, amountsIn); + } + } + } + + /// @notice Calculate the missing variable in the yield space equation given the direction (PT in vs. out) + /// @dev We round in favor of the LPs, meaning that traders get slightly worse prices than they would if we had full + /// precision. However, the differences are small (on the order of 1e-11), and should only matter for very small trades. + function _onSwap( + bool pTIn, + bool givenIn, + uint256 amountDelta, + uint256 reservesTokenIn, + uint256 reservesTokenOut + ) internal view returns (uint256) { + // xPre = token in reserves pre swap + // yPre = token out reserves pre swap + + // Seconds until maturity, in 18 decimals + // After maturity, this pool becomes a constant sum AMM + uint256 ttm = maturity > block.timestamp ? uint256(maturity - block.timestamp) * FixedPoint.ONE : 0; + + // Time shifted partial `t` from the yieldspace paper (`ttm` adjusted by some factor `ts`) + uint256 t = ts.mulDown(ttm); + + // Full `t` with fees baked in + uint256 a = (pTIn ? g2 : g1).mulUp(t).complement(); + + // Pow up for `x1` & `y1` and down for `xOrY2` causes the pow induced error for `xOrYPost` + // to tend towards higher values rather than lower. + // Effectively we're adding a little bump up for ammountIn, and down for amountOut + + // x1 = xPre ^ a; y1 = yPre ^ a + uint256 x1 = reservesTokenIn.powUp(a); + uint256 y1 = reservesTokenOut.powUp(a); + + // y2 = (yPre - amountOut) ^ a; x2 = (xPre + amountIn) ^ a + // + // No overflow risk in the addition as Balancer will only allow an `amountDelta` for tokens coming in + // if the user actually has it, and the max token supply for well-behaved tokens is bounded by the uint256 type + uint256 newReservesTokenInOrOut = givenIn ? reservesTokenIn + amountDelta : reservesTokenOut.sub(amountDelta); + uint256 xOrY2 = newReservesTokenInOrOut.powDown(a); + + // x1 + y1 = xOrY2 + xOrYPost ^ a + // -> xOrYPost ^ a = x1 + y1 - x2 + // -> xOrYPost = (x1 + y1 - xOrY2) ^ (1 / a) + uint256 xOrYPost = (x1.add(y1).sub(xOrY2)).powUp(FixedPoint.ONE.divDown(a)); + _require(!givenIn || reservesTokenOut > xOrYPost, Errors.SWAP_TOO_SMALL); + + if (givenIn) { + // Check that PT reserves are greater than "Underlying" reserves per section 6.3 of the YS paper + _require( + pTIn ? + newReservesTokenInOrOut >= xOrYPost : + newReservesTokenInOrOut <= xOrYPost, + Errors.NEGATIVE_RATE + ); + + // amountOut = yPre - yPost + return reservesTokenOut.sub(xOrYPost); + } else { + _require( + pTIn ? + xOrYPost >= newReservesTokenInOrOut : + xOrYPost <= newReservesTokenInOrOut, + Errors.NEGATIVE_RATE + ); + + // amountIn = xPost - xPre + return xOrYPost.sub(reservesTokenIn); + } + } + + /* ========== PROTOCOL FEE HELPERS ========== */ + + /// @notice Determine the growth in the invariant due to swap fees only + /// @dev This can't be a view function b/c `Adapter.scale` is not a view function + function _bptFeeDue(uint256[] memory reserves, uint256 protocolSwapFeePercentage) internal view returns (uint256) { + uint256 ttm = maturity > block.timestamp ? uint256(maturity - block.timestamp) * FixedPoint.ONE : 0; + uint256 a = ts.mulDown(ttm).complement(); + + // Invariant growth from time only + uint256 timeOnlyInvariant = _lastToken0Reserve.powDown(a).add(_lastToken1Reserve.powDown(a)); + + // `x` & `y` for the actual invariant, with growth from time and fees + uint256 x = reserves[pti].add(totalSupply()).powDown(a); + uint256 y = reserves[1 - pti].mulDown(_initScale).powDown(a); + uint256 fullInvariant = x.add(y); + + if (fullInvariant <= timeOnlyInvariant) { + // Similar to the invariant check in balancer-v2-monorepo/**/WeightedMath.sol, + // this shouldn't happen outside of rounding errors, yet we keep this so that those + // potential errors don't lead to a locked state + return 0; + } + + // The formula to calculate fees due is: + // + // where: + // `g` is the factor by which reserves have grown + // `time-only invariant` = x^a + y^a + // `realized invariant` = (g*x)^a + (g*y)^a + // + // / realized invariant \ ^ (1/a) + // `growth` = | ---------------------- | + // \ time-only invariant / + // + // + // This gets us the proportional growth of all token balances, or `growth` + // + // We can plug this into the following equation from `WeightedMath` in PR#1111 on the Balancer monorepo: + // + // supply * protocol fee * (growth - 1) + // --------------------------- + // growth + // toMint = -------------------------------------- + // 1 - protocol fee * (growth - 1) + // --------------------------- + // growth + + uint256 growth = fullInvariant.divDown(timeOnlyInvariant).powDown(FixedPoint.ONE.divDown(a)); + uint256 k = protocolSwapFeePercentage.mulDown(growth.sub(FixedPoint.ONE)).divDown(growth); + + return totalSupply().mulDown(k).divDown(k.complement()); + } + + /// @notice Cache the given reserve amounts + /// @dev if the oracle is set, this function will also cache the invariant and supply + function _cacheReserves(uint256[] memory reserves) internal { + uint256 reservePT = reserves[pti].add(totalSupply()); + // Calculate the backdated Target reserve + uint256 reserveUnderlying = reserves[1 - pti].mulDown(_initScale); + + // Caclulate the invariant and store everything + uint256 lastToken0Reserve; + uint256 lastToken1Reserve; + if (pti == 0) { + lastToken0Reserve = reservePT; + lastToken1Reserve = reserveUnderlying; + } else { + lastToken0Reserve = reserveUnderlying; + lastToken1Reserve = reservePT; + } + + if (oracleData.oracleEnabled) { + // If the oracle is enabled, cache the current invarant as well so that callers can determine liquidity + uint256 ttm = maturity > block.timestamp ? uint256(maturity - block.timestamp) * FixedPoint.ONE : 0; + uint256 a = ts.mulDown(ttm).complement(); + + oracleData.logInvariant = int200( + LogCompression.toLowResLog( + lastToken0Reserve.powDown(a).add(lastToken1Reserve.powDown(a)) + ) + ); + } + + _lastToken0Reserve = lastToken0Reserve; + _lastToken1Reserve = lastToken1Reserve; + } + + /* ========== ORACLE HELPERS ========== */ + + /// @notice Update the oracle with the current index and timestamp + /// @dev Must receive reserves that have already been upscaled + /// @dev Acts as a no-op if: + /// * the oracle is not enabled + /// * a price has already been stored for this block + /// * the Target side of the pool doesn't have enough liquidity + function _updateOracle( + uint256 lastChangeBlock, + uint256 balancePT, + uint256 balanceTarget + ) internal { + // The Target side of the pool must have at least 0.01 units of liquidity for us to collect a price sample + // note additional liquidity contraints may be enforced outside of this contract via the invariant TWAP + if (oracleData.oracleEnabled && block.number > lastChangeBlock && balanceTarget >= 1e16) { + // Use equation (2) from the YieldSpace paper to calculate the the marginal rate from the reserves + uint256 impliedRate = balancePT.add(totalSupply()) + .divDown(balanceTarget.mulDown(_initScale)); + + // Guard against rounding from exits leading the implied rate to be very slightly negative + // NOTE: in a future version of this system, a postive rate invariant for joins/exits will be preserved, + // as is currently done for swaps + impliedRate = impliedRate < FixedPoint.ONE ? 0 : impliedRate.sub(FixedPoint.ONE); + + // Cacluate the price of one PT in Target terms + uint256 pTPriceInTarget = getPriceFromImpliedRate(impliedRate); + + // Following Balancer's oracle conventions, get price of token 1 in terms of token 0 and + // and the price of one BPT in terms of token 0 + // + // note b/c reserves are upscaled coming into this function, + // price is already upscaled to 18 decimals, regardless of the decimals used for token 0 & 1 + uint256 pairPrice = pti == 0 ? FixedPoint.ONE.divDown(pTPriceInTarget) : pTPriceInTarget; + + uint256 oracleUpdatedIndex = _processPriceData( + oracleData.oracleSampleInitialTimestamp, + oracleData.oracleIndex, + LogCompression.toLowResLog(pairPrice), + // We diverge from Balancer's defaults here by storing implied rate + // rather than BPT price in this second slot + // + // Also note implied rates of less than 1e6 are taken as 1e6, b/c: + // 1) `toLowResLog` fails for 0 and 1e6 is precise enough for our needs + // 2) 1e6 is the lowest value Balancer passes into this util (min for totalSupply()) + impliedRate < 1e6 ? LogCompression.toLowResLog(1e6) : LogCompression.toLowResLog(impliedRate), + int256(oracleData.logInvariant) + ); + + if (oracleData.oracleIndex != oracleUpdatedIndex) { + oracleData.oracleSampleInitialTimestamp = uint32(block.timestamp); + oracleData.oracleIndex = uint16(oracleUpdatedIndex); + } + } + } + + function _getOracleIndex() internal view override returns (uint256) { + return oracleData.oracleIndex; + } + + /* ========== PUBLIC GETTERS ========== */ + + /// @notice Get the APY implied rate for PTs given a price in Target + /// @param pTPriceInTarget price of PTs in terms of Target + function getImpliedRateFromPrice(uint256 pTPriceInTarget) public view returns (uint256 impliedRate) { + if (block.timestamp >= maturity) { + return 0; + } + + // Calculate the *normed* implied rate from the PT price + // (i.e. the effective implied rate of PTs over the period normed by the timeshift param) + // (e.g. PTs = 0.9 [U], time to maturity of 0.5 yrs, timeshift param of 10 yrs, the + // normed implied rate = ( 1 / 0.9 ) ^ ( 1 / (0.5 * [1 / 10]) ) - 1 = 722.5% ) + impliedRate = FixedPoint.ONE + .divDown(pTPriceInTarget.mulDown(AdapterLike(adapter).scaleStored())) + .powDown(FixedPoint.ONE.divDown(ts).divDown((maturity - block.timestamp) * FixedPoint.ONE)) + .sub(FixedPoint.ONE); + } + + /// @notice Get price of PTs in Target terms given a price for PTs in Target + /// @param impliedRate Normed implied rate + function getPriceFromImpliedRate(uint256 impliedRate) public view returns (uint256 pTPriceInTarget) { + if (block.timestamp >= maturity) { + return FixedPoint.ONE; + } + + // Calculate the PT price in Target from an implied rate adjusted by the timeshift param, + // where the timeshift is a normalization factor applied to the time to maturity + pTPriceInTarget = FixedPoint.ONE + .divDown(impliedRate.add(FixedPoint.ONE) + .powDown(((maturity - block.timestamp) * FixedPoint.ONE) + .divDown(FixedPoint.ONE.divDown(ts)))) + .divDown(AdapterLike(adapter).scaleStored()); + } + + /// @notice Get the "fair" price for the BPT tokens given a correct price for PTs + /// in terms of Target. i.e. the price of one BPT in terms of Target using reserves + /// as they would be if they accurately reflected the true PT price + /// @dev for a technical explanation of the concept, see the description in the following repo: + /// https://github.com/makerdao/univ2-lp-oracle/blob/874a59d74d847909cc4a31f0d38ee6b020f6525f/src/UNIV2LPOracle.sol#L26 + function getFairBPTPrice(uint256 ptTwapDuration) + public + view + returns (uint256 fairBptPriceInTarget) + { + OracleAverageQuery[] memory queries = new OracleAverageQuery[](1); + queries[0] = OracleAverageQuery({ + variable: Variable.PAIR_PRICE, + secs: ptTwapDuration, + ago: 1 hours // take the oracle from 1 hour ago + ptTwapDuration ago to 1 hour ago + }); + + // TWAP read will revert with ORACLE_NOT_INITIALIZED if the buffer has not been filled + uint256[] memory results = this.getTimeWeightedAverage(queries); + uint256 pTPriceInTarget = pti == 1 ? results[0] : FixedPoint.ONE.divDown(results[0]); + + uint256 impliedRate = getImpliedRateFromPrice(pTPriceInTarget); + (, uint256[] memory balances, ) = _vault.getPoolTokens(_poolId); + + uint256 ttm = maturity > block.timestamp + ? uint256(maturity - block.timestamp) * FixedPoint.ONE + : 0; + uint256 a = ts.mulDown(ttm).complement(); + + uint256 k = balances[pti].add(totalSupply()).powDown(a).add( + balances[1 - pti].mulDown(_initScale).powDown(a) + ); + + // Equilibrium reserves for the PT side, w/o the final `- totalSupply` at the end + uint256 equilibriumPTReservesPartial = k.divDown( + FixedPoint.ONE.divDown(FixedPoint.ONE.add(impliedRate).powDown(a)).add(FixedPoint.ONE) + ).powDown(FixedPoint.ONE.divDown(a)); + + uint256 equilibriumTargetReserves = equilibriumPTReservesPartial + .divDown(_initScale.mulDown(FixedPoint.ONE.add(impliedRate))); + + fairBptPriceInTarget = equilibriumTargetReserves + // Complete the equilibrium PT reserve calc + .add(equilibriumPTReservesPartial.sub(totalSupply()) + .mulDown(pTPriceInTarget)).divDown(totalSupply()); + } + + /// @notice Get token indices for PT and Target + function getIndices() public view returns (uint256 _pti, uint256 _targeti) { + _pti = pti; + _targeti = 1 - pti; + } + + /* ========== BALANCER REQUIRED INTERFACE ========== */ + + function getPoolId() public view override returns (bytes32) { + return _poolId; + } + + function getVault() public view returns (IVault) { + return _vault; + } + + /* ========== BALANCER SCALING FUNCTIONS ========== */ + + /// @notice Scaling factors for PT & Target tokens + function _scalingFactor(bool pt) internal view returns (uint256) { + return pt ? _scalingFactorPT : _scalingFactorTarget; + } + + /// @notice Scale number type to 18 decimals if need be + function _upscale(uint256 amount, uint256 scalingFactor) internal pure returns (uint256) { + return BasicMath.mul(amount, scalingFactor); + } + + /// @notice Ensure number type is back in its base decimal if need be, rounding down + function _downscaleDown(uint256 amount, uint256 scalingFactor) internal pure returns (uint256) { + return amount / scalingFactor; + } + + /// @notice Ensure number type is back in its base decimal if need be, rounding up + function _downscaleUp(uint256 amount, uint256 scalingFactor) internal pure returns (uint256) { + return BasicMath.divUp(amount, scalingFactor); + } + + /// @notice Upscale array of token amounts to 18 decimals if need be + function _upscaleArray(uint256[] memory amounts) internal view { + amounts[pti] = BasicMath.mul(amounts[pti], _scalingFactor(true)); + amounts[1 - pti] = BasicMath.mul(amounts[1 - pti], _scalingFactor(false)); + } + + /// @notice Downscale array of token amounts to 18 decimals if need be, rounding down + function _downscaleDownArray(uint256[] memory amounts) internal view { + amounts[pti] = amounts[pti] / _scalingFactor(true); + amounts[1 - pti] = amounts[1 - pti] / _scalingFactor(false); + } + /// @notice Downscale array of token amounts to 18 decimals if need be, rounding up + function _downscaleUpArray(uint256[] memory amounts) internal view { + amounts[pti] = BasicMath.divUp(amounts[pti], _scalingFactor(true)); + amounts[1 - pti] = BasicMath.divUp(amounts[1 - pti], _scalingFactor(false)); + } + + /* ========== MODIFIERS ========== */ + + /// Taken from balancer-v2-monorepo/**/WeightedPool2Tokens.sol + modifier onlyVault(bytes32 poolId_) { + _require(msg.sender == address(getVault()), Errors.CALLER_NOT_VAULT); + _require(poolId_ == getPoolId(), Errors.INVALID_POOL_ID); + _; + } +} \ No newline at end of file diff --git a/semgrep-rules/security/sense-missing-oracle-access-control.yaml b/semgrep-rules/security/sense-missing-oracle-access-control.yaml new file mode 100644 index 0000000..499094d --- /dev/null +++ b/semgrep-rules/security/sense-missing-oracle-access-control.yaml @@ -0,0 +1,52 @@ +rules: +- + id: sense-missing-oracle-access-control + message: Oracle update is not restricted in $F() + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: MEDIUM + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + author: https://twitter.com/ArbazKiraak + references: + - https://medium.com/immunefi/sense-finance-access-control-issue-bugfix-review-32e0c806b1a0 + patterns: + - pattern-either: + - pattern-inside: | + function $F(...,$D $REQUEST,...) external { + ... + } + - pattern-inside: | + function $F(...,$D $REQUEST,...) public { + ... + } + - pattern-not-inside: | + function $F(...,$D $REQUEST,...) external onlyVault(...) { + ... + } + - patterns: + - pattern: _updateOracle($LASTBLOCK,...,...) + - pattern-not-inside: | + ... + if (msg.sender == $BALANCER) { ... } + ... + - pattern-not-inside: | + ... + require(msg.sender == address($BALANCER),...); + ... + - pattern-not-inside: | + ... + if (_msgSender() == $BALANCER) { ... } + ... + - pattern-not-inside: | + ... + require(_msgSender() == address($BALANCER),...); + ... + languages: + - solidity + severity: ERROR diff --git a/semgrep-rules/security/superfluid-ctx-injection.sol b/semgrep-rules/security/superfluid-ctx-injection.sol new file mode 100644 index 0000000..3c33745 --- /dev/null +++ b/semgrep-rules/security/superfluid-ctx-injection.sol @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: AGPLv3 +pragma solidity 0.7.6; +pragma experimental ABIEncoderV2; + +import { + ISuperfluidGovernance, + ISuperfluid, + ISuperfluidToken, + ISuperApp, + SuperAppDefinitions, + ContextDefinitions +} from "../interfaces/superfluid/ISuperfluid.sol"; +import { ISuperfluidToken } from "../interfaces/superfluid/ISuperfluidToken.sol"; + +import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol"; + + +/** + * @dev Helper library for building super agreement + */ +library AgreementLibrary { + + using SignedSafeMath for int256; + using SafeCast for uint256; + using SafeCast for int256; + + /************************************************************************** + * Context helpers + *************************************************************************/ + + /** + * @dev Authorize the msg.sender to access token agreement storage + * + * NOTE: + * - msg.sender must be the expected host contract. + * - it should revert on unauthorized access. + */ + function authorizeTokenAccess(ISuperfluidToken token, bytes memory ctx) + internal view + returns (ISuperfluid.Context memory) + { + require(token.getHost() == msg.sender, "AgreementLibrary: unauthroized host"); + // ruleid: superfluid-ctx-injection + return ISuperfluid(msg.sender).decodeCtx(ctx); + } + + /************************************************************************** + * Agreement callback helpers + *************************************************************************/ + + struct CallbackInputs { + ISuperfluidToken token; + address account; + bytes32 agreementId; + bytes agreementData; + uint256 appAllowanceGranted; + int256 appAllowanceUsed; + uint256 noopBit; + } + + function createCallbackInputs( + ISuperfluidToken token, + address account, + bytes32 agreementId, + bytes memory agreementData + ) + internal pure + returns (CallbackInputs memory inputs) + { + inputs.token = token; + inputs.account = account; + inputs.agreementId = agreementId; + inputs.agreementData = agreementData; + } + + function callAppBeforeCallback( + CallbackInputs memory inputs, + bytes memory ctx + ) + internal + returns(bytes memory cbdata) + { + bool isSuperApp; + bool isJailed; + uint256 noopMask; + (isSuperApp, isJailed, noopMask) = ISuperfluid(msg.sender).getAppManifest(ISuperApp(inputs.account)); + if (isSuperApp && !isJailed) { + bytes memory appCtx = _pushCallbackStack(ctx, inputs); + if ((noopMask & inputs.noopBit) == 0) { + bytes memory callData = abi.encodeWithSelector( + _selectorFromNoopBit(inputs.noopBit), + inputs.token, + address(this) /* agreementClass */, + inputs.agreementId, + inputs.agreementData, + new bytes(0) // placeholder ctx + ); + cbdata = ISuperfluid(msg.sender).callAppBeforeCallback( + ISuperApp(inputs.account), + callData, + inputs.noopBit == SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP, + appCtx); + } + _popCallbackStack(ctx, 0); + } + } + + function callAppAfterCallback( + CallbackInputs memory inputs, + bytes memory cbdata, + bytes memory ctx + ) + internal + returns (ISuperfluid.Context memory appContext, bytes memory newCtx) + { + bool isSuperApp; + bool isJailed; + uint256 noopMask; + (isSuperApp, isJailed, noopMask) = ISuperfluid(msg.sender).getAppManifest(ISuperApp(inputs.account)); + + if (isSuperApp && !isJailed) { + newCtx = _pushCallbackStack(ctx, inputs); + if ((noopMask & inputs.noopBit) == 0) { + bytes memory callData = abi.encodeWithSelector( + _selectorFromNoopBit(inputs.noopBit), + inputs.token, + address(this) /* agreementClass */, + inputs.agreementId, + inputs.agreementData, + cbdata, + new bytes(0) // placeholder ctx + ); + newCtx = ISuperfluid(msg.sender).callAppAfterCallback( + ISuperApp(inputs.account), + callData, + inputs.noopBit == SuperAppDefinitions.AFTER_AGREEMENT_TERMINATED_NOOP, + newCtx); + + appContext = ISuperfluid(msg.sender).decodeCtx(newCtx); + + // adjust allowance used to the range [appAllowanceWanted..appAllowanceGranted] + appContext.appAllowanceUsed = max(0, min( + inputs.appAllowanceGranted.toInt256(), + max(appContext.appAllowanceWanted.toInt256(), appContext.appAllowanceUsed))); + + } + newCtx = _popCallbackStack(ctx, appContext.appAllowanceUsed); + } + } + + function _selectorFromNoopBit(uint256 noopBit) + private pure + returns (bytes4 selector) + { + if (noopBit == SuperAppDefinitions.BEFORE_AGREEMENT_CREATED_NOOP) { + return ISuperApp.beforeAgreementCreated.selector; + } else if (noopBit == SuperAppDefinitions.BEFORE_AGREEMENT_UPDATED_NOOP) { + return ISuperApp.beforeAgreementUpdated.selector; + } else if (noopBit == SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP) { + return ISuperApp.beforeAgreementTerminated.selector; + } else if (noopBit == SuperAppDefinitions.AFTER_AGREEMENT_CREATED_NOOP) { + return ISuperApp.afterAgreementCreated.selector; + } else if (noopBit == SuperAppDefinitions.AFTER_AGREEMENT_UPDATED_NOOP) { + return ISuperApp.afterAgreementUpdated.selector; + } else /* if (noopBit == SuperAppDefinitions.AFTER_AGREEMENT_TERMINATED_NOOP) */ { + return ISuperApp.afterAgreementTerminated.selector; + } + } + + function _pushCallbackStack( + bytes memory ctx, + CallbackInputs memory inputs + ) + private + returns (bytes memory appCtx) + { + // app allowance params stack PUSH + // pass app allowance and current allowance used to the app, + appCtx = ISuperfluid(msg.sender).appCallbackPush( + ctx, + ISuperApp(inputs.account), + inputs.appAllowanceGranted, + inputs.appAllowanceUsed, + inputs.token); + } + + function _popCallbackStack( + bytes memory ctx, + int256 appAllowanceUsedDelta + ) + private + returns (bytes memory newCtx) + { + // app allowance params stack POP + return ISuperfluid(msg.sender).appCallbackPop(ctx, appAllowanceUsedDelta); + } + + /************************************************************************** + * Misc + *************************************************************************/ + + function max(int256 a, int256 b) internal pure returns (int256) { return a > b ? a : b; } + + function min(int256 a, int256 b) internal pure returns (int256) { return a > b ? b : a; } +} \ No newline at end of file diff --git a/semgrep-rules/security/superfluid-ctx-injection.yaml b/semgrep-rules/security/superfluid-ctx-injection.yaml new file mode 100644 index 0000000..54fc050 --- /dev/null +++ b/semgrep-rules/security/superfluid-ctx-injection.yaml @@ -0,0 +1,27 @@ +rules: + - + id: superfluid-ctx-injection + message: A specially crafted calldata may be used to impersonate other accounts + metadata: + category: security + technology: + - solidity + cwe: "CWE-20: Improper Input Validation" + confidence: HIGH + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://rekt.news/superfluid-rekt/ + - https://medium.com/superfluid-blog/08-02-22-exploit-post-mortem-15ff9c97cdd + - https://polygonscan.com/address/0x07711bb6dfbc99a1df1f2d7f57545a67519941e7 + patterns: + - pattern: $T.decodeCtx(ctx); + - pattern-not-inside: | + require($T.isCtxValid(...), "..."); + ... + languages: + - solidity + severity: ERROR + diff --git a/semgrep-rules/security/tecra-coin-burnfrom-bug.sol b/semgrep-rules/security/tecra-coin-burnfrom-bug.sol new file mode 100644 index 0000000..926596a --- /dev/null +++ b/semgrep-rules/security/tecra-coin-burnfrom-bug.sol @@ -0,0 +1,508 @@ +/** + *Submitted for verification at Etherscan.io on 2021-04-13 +*/ + +// SPDX-License-Identifier: Unlicense +pragma solidity 0.8.2; + +// interface need to claim rouge tokens from contract and handle upgraded functions +abstract contract IERC20 { + function balanceOf(address owner) public view virtual returns (uint256); + + function transfer(address to, uint256 amount) public virtual; + + function allowance(address owner, address spender) + public + view + virtual + returns (uint256); + + function totalSupply() public view virtual returns (uint256); +} + +// interface to potential future upgraded contract, +// only essential write functions that need check that this contract is caller +abstract contract IUpgradedToken { + function transferByLegacy( + address sender, + address to, + uint256 amount + ) public virtual returns (bool); + + function transferFromByLegacy( + address sender, + address from, + address to, + uint256 amount + ) public virtual returns (bool); + + function approveByLegacy( + address sender, + address spender, + uint256 amount + ) public virtual; +} + +// +// The ultimate ERC20 token contract for TecraCoin project +// +contract TcrToken { + // + // ERC20 basic information + // + uint8 public constant decimals = 8; + string public constant name = "TecraCoin"; + string public constant symbol = "TCR"; + uint256 private _totalSupply; + uint256 public constant maxSupply = 21000000000000000; + + string public constant version = "1"; + uint256 public immutable getChainId; + + // + // other flags, data and constants + // + address public owner; + address public newOwner; + + bool public paused; + + bool public deprecated; + address public upgradedAddress; + + bytes32 public immutable DOMAIN_SEPARATOR; + + // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + bytes32 public constant PERMIT_TYPEHASH = + 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; + + string private constant ERROR_DAS = "Different array sizes"; + string private constant ERROR_BTL = "Balance too low"; + string private constant ERROR_ATL = "Allowance too low"; + string private constant ERROR_OO = "Only Owner"; + + // + // events + // + event Transfer(address indexed from, address indexed to, uint256 value); + + event Paused(); + event Unpaused(); + + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); + + event AddedToBlacklist(address indexed account); + event RemovedFromBlacklist(address indexed account); + + // + // data stores + // + mapping(address => mapping(address => uint256)) private _allowances; + mapping(address => uint256) private _balances; + + mapping(address => bool) public isBlacklisted; + + mapping(address => bool) public isBlacklistAdmin; + mapping(address => bool) public isMinter; + mapping(address => bool) public isPauser; + + mapping(address => uint256) public nonces; + + // + // contract constructor + // + constructor() { + owner = msg.sender; + getChainId = block.chainid; + // EIP712 Domain + DOMAIN_SEPARATOR = keccak256( + abi.encode( + keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ), + keccak256(bytes(name)), + keccak256(bytes(version)), + block.chainid, + address(this) + ) + ); + } + + // + // "approve" + // + function approve(address spender, uint256 amount) external { + if (deprecated) { + return + IUpgradedToken(upgradedAddress).approveByLegacy( + msg.sender, + spender, + amount + ); + } + _approve(msg.sender, spender, amount); + } + + // + // "burnable" + // + function burn(uint256 amount) external { + require(_balances[msg.sender] >= amount, ERROR_BTL); + _burn(msg.sender, amount); + } + + function burnFrom(address from, uint256 amount) external { + // ruleid: tecra-coin-burnfrom-bug + require(_allowances[msg.sender][from] >= amount, ERROR_ATL); + require(_balances[from] >= amount, ERROR_BTL); + _approve(msg.sender, from, _allowances[msg.sender][from] - amount); + _burn(from, amount); + } + + function decreaseAllowance(address from, uint256 amount) public virtual returns (bool) { + // ok: tecra-coin-burnfrom-bug + require(_allowances[msg.sender][from] >= amount); + _approve(msg.sender, from, _allowances[msg.sender][from] - amount); + return true; + } + + // + // "transfer" + // + function transfer(address to, uint256 amount) external returns (bool) { + if (deprecated) { + return + IUpgradedToken(upgradedAddress).transferByLegacy( + msg.sender, + to, + amount + ); + } + require(_balances[msg.sender] >= amount, ERROR_BTL); + _transfer(msg.sender, to, amount); + return true; + } + + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool) { + if (deprecated) { + return + IUpgradedToken(upgradedAddress).transferFromByLegacy( + msg.sender, + from, + to, + amount + ); + } + _allowanceTransfer(msg.sender, from, to, amount); + return true; + } + + // + // non-ERC20 functionality + // + // Rouge tokens and ETH withdrawal + function acquire(address token) external onlyOwner { + if (token == address(0)) { + payable(owner).transfer(address(this).balance); + } else { + uint256 amount = IERC20(token).balanceOf(address(this)); + require(amount > 0, ERROR_BTL); + IERC20(token).transfer(owner, amount); + } + } + + // + // "blacklist" + // + function addBlacklister(address user) external onlyOwner { + isBlacklistAdmin[user] = true; + } + + function removeBlacklister(address user) external onlyOwner { + isBlacklistAdmin[user] = false; + } + + modifier onlyBlacklister { + require(isBlacklistAdmin[msg.sender], "Not a Blacklister"); + _; + } + + modifier notOnBlacklist(address user) { + require(!isBlacklisted[user], "Address on blacklist"); + _; + } + + function addBlacklist(address user) external onlyBlacklister { + isBlacklisted[user] = true; + emit AddedToBlacklist(user); + } + + function removeBlacklist(address user) external onlyBlacklister { + isBlacklisted[user] = false; + emit RemovedFromBlacklist(user); + } + + function burnBlackFunds(address user) external onlyOwner { + require(isBlacklisted[user], "Address not on blacklist"); + _burn(user, _balances[user]); + } + + // + // "bulk transfer" + // + // transfer to list of address-amount + function bulkTransfer(address[] calldata to, uint256[] calldata amount) + external + returns (bool) + { + require(to.length == amount.length, ERROR_DAS); + for (uint256 i = 0; i < to.length; i++) { + require(_balances[msg.sender] >= amount[i], ERROR_BTL); + _transfer(msg.sender, to[i], amount[i]); + } + return true; + } + + // transferFrom to list of address-amount + function bulkTransferFrom( + address from, + address[] calldata to, + uint256[] calldata amount + ) external returns (bool) { + require(to.length == amount.length, ERROR_DAS); + for (uint256 i = 0; i < to.length; i++) { + _allowanceTransfer(msg.sender, from, to[i], amount[i]); + } + return true; + } + + // send same amount to multiple addresses + function bulkTransfer(address[] calldata to, uint256 amount) + external + returns (bool) + { + require(_balances[msg.sender] >= amount * to.length, ERROR_BTL); + for (uint256 i = 0; i < to.length; i++) { + _transfer(msg.sender, to[i], amount); + } + return true; + } + + // send same amount to multiple addresses by allowance + function bulkTransferFrom( + address from, + address[] calldata to, + uint256 amount + ) external returns (bool) { + require(_balances[from] >= amount * to.length, ERROR_BTL); + for (uint256 i = 0; i < to.length; i++) { + _allowanceTransfer(msg.sender, from, to[i], amount); + } + return true; + } + + // + // "mint" + // + modifier onlyMinter { + require(isMinter[msg.sender], "Not a Minter"); + _; + } + + function addMinter(address user) external onlyOwner { + isMinter[user] = true; + } + + function removeMinter(address user) external onlyOwner { + isMinter[user] = false; + } + + function mint(address to, uint256 amount) external onlyMinter { + _balances[to] += amount; + _totalSupply += amount; + require(_totalSupply < maxSupply, "You can not mine that much"); + emit Transfer(address(0), to, amount); + } + + // + // "ownable" + // + modifier onlyOwner { + require(msg.sender == owner, ERROR_OO); + _; + } + + function giveOwnership(address _newOwner) external onlyOwner { + newOwner = _newOwner; + } + + function acceptOwnership() external { + require(msg.sender == newOwner, ERROR_OO); + newOwner = address(0); + owner = msg.sender; + } + + // + // "pausable" + // + function addPauser(address user) external onlyOwner { + isPauser[user] = true; + } + + function removePauser(address user) external onlyOwner { + isPauser[user] = false; + } + + modifier onlyPauser { + require(isPauser[msg.sender], "Not a Pauser"); + _; + } + + modifier notPaused { + require(!paused, "Contract is paused"); + _; + } + + function pause() external onlyPauser notPaused { + paused = true; + emit Paused(); + } + + function unpause() external onlyPauser { + require(paused, "Contract not paused"); + paused = false; + emit Unpaused(); + } + + // + // "permit" + // Uniswap integration EIP-2612 + // + function permit( + address user, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + require(deadline >= block.timestamp, "permit: EXPIRED"); + bytes32 digest = + keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256( + abi.encode( + PERMIT_TYPEHASH, + user, + spender, + value, + nonces[user]++, + deadline + ) + ) + ) + ); + address recoveredAddress = ecrecover(digest, v, r, s); + require( + recoveredAddress != address(0) && recoveredAddress == user, + "permit: INVALID_SIGNATURE" + ); + _approve(user, spender, value); + } + + // + // upgrade contract + // + function upgrade(address token) external onlyOwner { + deprecated = true; + upgradedAddress = token; + } + + // + // ERC20 view functions + // + function balanceOf(address account) external view returns (uint256) { + if (deprecated) { + return IERC20(upgradedAddress).balanceOf(account); + } + return _balances[account]; + } + + function allowance(address account, address spender) + external + view + returns (uint256) + { + if (deprecated) { + return IERC20(upgradedAddress).allowance(account, spender); + } + return _allowances[account][spender]; + } + + function totalSupply() external view returns (uint256) { + if (deprecated) { + return IERC20(upgradedAddress).totalSupply(); + } + return _totalSupply; + } + + // + // internal functions + // + function _approve( + address account, + address spender, + uint256 amount + ) private notOnBlacklist(account) notOnBlacklist(spender) notPaused { + _allowances[account][spender] = amount; + emit Approval(account, spender, amount); + } + + function _allowanceTransfer( + address spender, + address from, + address to, + uint256 amount + ) private { + require(_allowances[from][spender] >= amount, ERROR_ATL); + require(_balances[from] >= amount, ERROR_BTL); + + // exception for Uniswap "approve forever" + if (_allowances[from][spender] != type(uint256).max) { + _approve(from, spender, _allowances[from][spender] - amount); + } + + _transfer(from, to, amount); + } + + function _burn(address from, uint256 amount) private notPaused { + _balances[from] -= amount; + _totalSupply -= amount; + emit Transfer(from, address(0), amount); + } + + function _transfer( + address from, + address to, + uint256 amount + ) private notOnBlacklist(from) notOnBlacklist(to) notPaused { + require(to != address(0), "Use burn instead"); + require(from != address(0), "What a Terrible Failure"); + _balances[from] -= amount; + _balances[to] += amount; + emit Transfer(from, to, amount); + } +} + +// rav3n_pl was here \ No newline at end of file diff --git a/semgrep-rules/security/tecra-coin-burnfrom-bug.yaml b/semgrep-rules/security/tecra-coin-burnfrom-bug.yaml new file mode 100644 index 0000000..384b3ee --- /dev/null +++ b/semgrep-rules/security/tecra-coin-burnfrom-bug.yaml @@ -0,0 +1,31 @@ +rules: + - + id: tecra-coin-burnfrom-bug + message: Parameter "from" is checked at incorrect position in "_allowances" mapping + metadata: + category: security + technology: + - solidity + cwe: "CWE-688: Function Call With Incorrect Variable or Reference as Argument" + confidence: MEDIUM + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/Mauricio_0218/status/1490082073096462340 + - https://etherscan.io/address/0xe38b72d6595fd3885d1d2f770aa23e94757f91a1 + patterns: + - pattern-inside: | + function $BURN(..., address $FROM, ...) { + ... + _burn($FROM, ...); + ... + } + - pattern-either: + - pattern: require(_allowances[$S][$FROM] >= $X, ...) + - pattern: require(allowance($S, $FROM) >= $X, ...) + languages: + - solidity + severity: ERROR + diff --git a/semgrep-rules/security/thirdweb-vulnerability.sol b/semgrep-rules/security/thirdweb-vulnerability.sol new file mode 100644 index 0000000..81a1215 --- /dev/null +++ b/semgrep-rules/security/thirdweb-vulnerability.sol @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.11; + +//Interface +import { ITokenERC20 } from "../interfaces/token/ITokenERC20.sol"; + +import "../interfaces/IThirdwebContract.sol"; +import "../extension/interface/IPlatformFee.sol"; +import "../extension/interface/IPrimarySale.sol"; + +// Token +import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol"; + +// Security +import "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; + +// Signature utils +import "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol"; + +// Meta transactions +// ruleid: thirdweb-vulnerability +import "../openzeppelin-presets/metatx/ERC2771ContextUpgradeable.sol"; + +// Utils +import "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol"; +import "../lib/CurrencyTransferLib.sol"; +import "../lib/FeeType.sol"; + +contract TokenERC20 is + Initializable, + IThirdwebContract, + IPrimarySale, + IPlatformFee, + ReentrancyGuardUpgradeable, + ERC2771ContextUpgradeable, + MulticallUpgradeable, + ERC20BurnableUpgradeable, + ERC20VotesUpgradeable, + ITokenERC20, + AccessControlEnumerableUpgradeable +{ + using ECDSAUpgradeable for bytes32; + + bytes32 private constant MODULE_TYPE = bytes32("TokenERC20"); + uint256 private constant VERSION = 1; + + bytes32 private constant TYPEHASH = + keccak256( + "MintRequest(address to,address primarySaleRecipient,uint256 quantity,uint256 price,address currency,uint128 validityStartTimestamp,uint128 validityEndTimestamp,bytes32 uid)" + ); + + bytes32 internal constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 internal constant TRANSFER_ROLE = keccak256("TRANSFER_ROLE"); + + /// @dev Returns the URI for the storefront-level metadata of the contract. + string public contractURI; + + /// @dev Max bps in the thirdweb system + uint128 internal constant MAX_BPS = 10_000; + + /// @dev The % of primary sales collected by the contract as fees. + uint128 private platformFeeBps; + + /// @dev The adress that receives all primary sales value. + address internal platformFeeRecipient; + + /// @dev The adress that receives all primary sales value. + address public primarySaleRecipient; + + /// @dev Mapping from mint request UID => whether the mint request is processed. + mapping(bytes32 => bool) private minted; + + constructor() initializer {} + + /// @dev Initiliazes the contract, like a constructor. + function initialize( + address _defaultAdmin, + string memory _name, + string memory _symbol, + string memory _contractURI, + address[] memory _trustedForwarders, + address _primarySaleRecipient, + address _platformFeeRecipient, + uint256 _platformFeeBps + ) external initializer { + __ReentrancyGuard_init(); + __ERC2771Context_init_unchained(_trustedForwarders); + __ERC20Permit_init(_name); + __ERC20_init_unchained(_name, _symbol); + + contractURI = _contractURI; + primarySaleRecipient = _primarySaleRecipient; + platformFeeRecipient = _platformFeeRecipient; + + require(_platformFeeBps <= MAX_BPS, "exceeds MAX_BPS"); + platformFeeBps = uint128(_platformFeeBps); + + _setupRole(DEFAULT_ADMIN_ROLE, _defaultAdmin); + _setupRole(TRANSFER_ROLE, _defaultAdmin); + _setupRole(MINTER_ROLE, _defaultAdmin); + _setupRole(TRANSFER_ROLE, address(0)); + } + + /// @dev Returns the module type of the contract. + function contractType() external pure virtual returns (bytes32) { + return MODULE_TYPE; + } + + /// @dev Returns the version of the contract. + function contractVersion() external pure virtual returns (uint8) { + return uint8(VERSION); + } + + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual override(ERC20Upgradeable, ERC20VotesUpgradeable) { + super._afterTokenTransfer(from, to, amount); + } + + /// @dev Runs on every transfer. + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal override { + super._beforeTokenTransfer(from, to, amount); + + if (!hasRole(TRANSFER_ROLE, address(0)) && from != address(0) && to != address(0)) { + require(hasRole(TRANSFER_ROLE, from) || hasRole(TRANSFER_ROLE, to), "transfers restricted."); + } + } + + function _mint(address account, uint256 amount) internal virtual override(ERC20Upgradeable, ERC20VotesUpgradeable) { + super._mint(account, amount); + } + + function _burn(address account, uint256 amount) internal virtual override(ERC20Upgradeable, ERC20VotesUpgradeable) { + super._burn(account, amount); + } + + /** + * @dev Creates `amount` new tokens for `to`. + * + * See {ERC20-_mint}. + * + * Requirements: + * + * - the caller must have the `MINTER_ROLE`. + */ + function mintTo(address to, uint256 amount) public virtual { + require(hasRole(MINTER_ROLE, _msgSender()), "not minter."); + _mintTo(to, amount); + } + + /// @dev Verifies that a mint request is signed by an account holding MINTER_ROLE (at the time of the function call). + function verify(MintRequest calldata _req, bytes calldata _signature) public view returns (bool, address) { + address signer = recoverAddress(_req, _signature); + return (!minted[_req.uid] && hasRole(MINTER_ROLE, signer), signer); + } + + /// @dev Mints tokens according to the provided mint request. + function mintWithSignature(MintRequest calldata _req, bytes calldata _signature) external payable nonReentrant { + address signer = verifyRequest(_req, _signature); + address receiver = _req.to; + + collectPrice(_req); + + _mintTo(receiver, _req.quantity); + + emit TokensMintedWithSignature(signer, receiver, _req); + } + + /// @dev Lets a module admin set the default recipient of all primary sales. + function setPrimarySaleRecipient(address _saleRecipient) external onlyRole(DEFAULT_ADMIN_ROLE) { + primarySaleRecipient = _saleRecipient; + emit PrimarySaleRecipientUpdated(_saleRecipient); + } + + /// @dev Lets a module admin update the fees on primary sales. + function setPlatformFeeInfo(address _platformFeeRecipient, uint256 _platformFeeBps) + external + onlyRole(DEFAULT_ADMIN_ROLE) + { + require(_platformFeeBps <= MAX_BPS, "exceeds MAX_BPS"); + + platformFeeBps = uint64(_platformFeeBps); + platformFeeRecipient = _platformFeeRecipient; + + emit PlatformFeeInfoUpdated(_platformFeeRecipient, _platformFeeBps); + } + + /// @dev Returns the platform fee bps and recipient. + function getPlatformFeeInfo() external view returns (address, uint16) { + return (platformFeeRecipient, uint16(platformFeeBps)); + } + + /// @dev Collects and distributes the primary sale value of tokens being claimed. + function collectPrice(MintRequest calldata _req) internal { + if (_req.price == 0) { + return; + } + + uint256 platformFees = (_req.price * platformFeeBps) / MAX_BPS; + + if (_req.currency == CurrencyTransferLib.NATIVE_TOKEN) { + require(msg.value == _req.price, "must send total price."); + } else { + require(msg.value == 0, "msg value not zero"); + } + + address saleRecipient = _req.primarySaleRecipient == address(0) + ? primarySaleRecipient + : _req.primarySaleRecipient; + + CurrencyTransferLib.transferCurrency(_req.currency, _msgSender(), platformFeeRecipient, platformFees); + CurrencyTransferLib.transferCurrency(_req.currency, _msgSender(), saleRecipient, _req.price - platformFees); + } + + /// @dev Mints `amount` of tokens to `to` + function _mintTo(address _to, uint256 _amount) internal { + _mint(_to, _amount); + emit TokensMinted(_to, _amount); + } + + /// @dev Verifies that a mint request is valid. + function verifyRequest(MintRequest calldata _req, bytes calldata _signature) internal returns (address) { + (bool success, address signer) = verify(_req, _signature); + require(success, "invalid signature"); + + require( + _req.validityStartTimestamp <= block.timestamp && _req.validityEndTimestamp >= block.timestamp, + "request expired" + ); + require(_req.to != address(0), "recipient undefined"); + require(_req.quantity > 0, "zero quantity"); + + minted[_req.uid] = true; + + return signer; + } + + /// @dev Returns the address of the signer of the mint request. + function recoverAddress(MintRequest calldata _req, bytes calldata _signature) internal view returns (address) { + return _hashTypedDataV4(keccak256(_encodeRequest(_req))).recover(_signature); + } + + /// @dev Resolves 'stack too deep' error in `recoverAddress`. + function _encodeRequest(MintRequest calldata _req) internal pure returns (bytes memory) { + return + abi.encode( + TYPEHASH, + _req.to, + _req.primarySaleRecipient, + _req.quantity, + _req.price, + _req.currency, + _req.validityStartTimestamp, + _req.validityEndTimestamp, + _req.uid + ); + } + + /// @dev Sets contract URI for the storefront-level metadata of the contract. + function setContractURI(string calldata _uri) external onlyRole(DEFAULT_ADMIN_ROLE) { + contractURI = _uri; + } + + function _msgSender() + internal + view + virtual + override(ContextUpgradeable, ERC2771ContextUpgradeable) + returns (address sender) + { + return ERC2771ContextUpgradeable._msgSender(); + } + + function _msgData() + internal + view + virtual + override(ContextUpgradeable, ERC2771ContextUpgradeable) + returns (bytes calldata) + { + return ERC2771ContextUpgradeable._msgData(); + } +} diff --git a/semgrep-rules/security/thirdweb-vulnerability.yaml b/semgrep-rules/security/thirdweb-vulnerability.yaml new file mode 100644 index 0000000..1d668f8 --- /dev/null +++ b/semgrep-rules/security/thirdweb-vulnerability.yaml @@ -0,0 +1,24 @@ +rules: +- id: thirdweb-vulnerability + pattern-either: + - pattern-regex: '(?i)import ".*\/(.*)ERC2771(.*)\.sol";((.|\n)*)import ".*\/(.*)Multicall(.*)\.sol";' + - pattern-regex: '(?i)import ".*\/(.*)Multicall(.*)\.sol";((.|\n)*)import ".*\/(.*)ERC2771(.*)\.sol";' + message: | + In contracts that support Multicall and ERC2771Context an Arbitrary Address Spoofing attack is possible. + languages: [solidity] + severity: ERROR + metadata: + category: security + technology: + - solidity + cwe: "CWE-937: Using Components with Known Vulnerabilities" + confidence: HIGH + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://blog.thirdweb.com/vulnerability-report/ + - https://dedaub.com/blog/critical-thirdweb-vulnerability + - https://blog.openzeppelin.com/arbitrary-address-spoofing-vulnerability-erc2771context-multicall-public-disclosure + diff --git a/semgrep-rules/security/uniswap-callback-not-protected.sol b/semgrep-rules/security/uniswap-callback-not-protected.sol new file mode 100644 index 0000000..11dcb07 --- /dev/null +++ b/semgrep-rules/security/uniswap-callback-not-protected.sol @@ -0,0 +1,318 @@ +contract Test { + // ok: uniswap-callback-not-protected + function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external { + + require(msg.sender == factory.getPair(IUniswapV2Pair(msg.sender).token0(), IUniswapV2Pair(msg.sender).token1())); + + (address borrower, + address collateralMarket, + address borrowMarket, + uint amount, + address collateralToken, + address borrowToken, + bool isEthCollateralMarket, + bool isEthBorrowMarket) = abi.decode(data, (address, address, address, uint, address, address, bool, bool)); + + if (isEthBorrowMarket) { + IWETH(WETH).withdraw(amount); + } + + + IERC20(borrowToken).safeApprove(liquidateLends,amount); + bytes memory callData = abi.encodeWithSignature("doLiquidate(address,address,address,uint256,address,address)", + borrower, + collateralMarket, + borrowMarket, + amount, + isEthCollateralMarket ? ETH : collateralToken, + isEthBorrowMarket ? ETH : borrowToken + ); + liquidateLends.call(callData); + + + if (isEthCollateralMarket) { + IWETH(WETH).deposit{value : address(this).balance}(); + } + + _payBackPair(msg.sender, amount0, amount1, borrowToken, collateralToken); + } + + // ruleid: uniswap-callback-not-protected + function uniswapV2Call(address payable _sender, uint _amount0, uint _amount1, bytes calldata _data) external { + // access control + emit Log("uniswapV2Call"); + // decode data + ( + SwapType _swapType, + address _tokenBorrow, + address _pairAddress, + address _tokenPay, + uint _amount, + bool _isBorrowingEth, + bool _isPayingEth, + bytes memory _triangleData, + bytes memory _userData + ) = abi.decode(_data, (SwapType, address, address, address, uint, bool, bool, bytes, bytes)); + + permissionedPairAddress = _pairAddress; + console.log("SENDER", msg.sender); + console.log("permissionedPairAddress", permissionedPairAddress); + // require(msg.sender == permissionedPairAddress, "only permissioned UniswapV2 pair can call"); + require(_sender == address(this), "only this contract may initiate"); + + address[] memory _tokenPath = new address[](2); + + address token0 = IUniswapV2Pair(_pairAddress).token0(); + address token1 = IUniswapV2Pair(_pairAddress).token1(); + + _tokenPath[0] = (token0 == WETH) ? token0 : token1; + _tokenPath[1] = (token1 == WETH) ? token1 : token0; + + if (_swapType == SwapType.SimpleLoan) { + simpleFlashLoanExecute( + _tokenBorrow, + msg.sender, + _amount, + _isBorrowingEth, + _isPayingEth, + _userData); + return; + } else if (_swapType == SwapType.SimpleSwap) { + simpleFlashSwapExecute(_tokenBorrow, _amount, _tokenPay, msg.sender, _isBorrowingEth, _isPayingEth, _userData); + return; + } else { + traingularFlashSwapExecute(_tokenBorrow, _pairAddress, _tokenPay, _amount, _triangleData, _userData); + } + + // NOOP to silence compiler "unused parameter" warning + if (false) { + _amount0; + _amount1; + } + } + + // ok: uniswap-callback-not-protected + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata _data + ) external override { + require(amount0Delta > 0 || amount1Delta > 0); // swaps entirely within 0-liquidity regions are not supported + SwapCallbackData memory data = abi.decode(_data, (SwapCallbackData)); + (address tokenIn, address tokenOut, uint24 fee) = data.path.decodeFirstPool(); + CallbackValidation.verifyCallback(factory, tokenIn, tokenOut, fee); + + (bool isExactInput, uint256 amountToPay) = + amount0Delta > 0 + ? (tokenIn < tokenOut, uint256(amount0Delta)) + : (tokenOut < tokenIn, uint256(amount1Delta)); + if (isExactInput) { + pay(tokenIn, data.payer, msg.sender, amountToPay); + } else { + // either initiate the next swap or pay + if (data.path.hasMultiplePools()) { + data.path = data.path.skipToken(); + exactOutputInternal(amountToPay, msg.sender, 0, data); + } else { + amountInCached = amountToPay; + tokenIn = tokenOut; // swap in/out because exact output swaps are reversed + pay(tokenIn, data.payer, msg.sender, amountToPay); + } + } + } + + // ok: uniswap-callback-not-protected + function uniswapV3SwapCallback(int256 _amount0Delta, int256 _amount1Delta, bytes calldata _data) + external + override(IUniswapV3SwapCallback) + { + // Swaps entirely within 0-liquidity regions are not supported + if (_amount0Delta <= 0 && _amount1Delta <= 0) revert InvalidDeltaAmounts(); + // Uniswap pools always call callback on msg.sender so this check is enough to prevent malicious behavior + if (msg.sender == LUSD_USDC_POOL) { + SwapCallbackData memory data = abi.decode(_data, (SwapCallbackData)); + // Repay debt in full + (address upperHint, address lowerHint) = _getHints(); + BORROWER_OPERATIONS.adjustTrove(0, data.collToWithdraw, data.debtToRepay, false, upperHint, lowerHint); + + // Pay LUSD_USDC_POOL for the swap by passing it as a recipient to the next swap (WETH -> USDC) + IUniswapV3PoolActions(USDC_ETH_POOL).swap( + LUSD_USDC_POOL, // recipient + false, // zeroForOne + -_amount1Delta, // amount of USDC to pay to LUSD_USDC_POOL for the swap + SQRT_PRICE_LIMIT_X96, + "" + ); + } else if (msg.sender == USDC_ETH_POOL) { + // Pay USDC_ETH_POOL for the USDC + uint256 amountToPay = uint256(_amount1Delta); + IWETH(WETH).deposit{value: amountToPay}(); // wrap only what is needed to pay + IWETH(WETH).transfer(address(USDC_ETH_POOL), amountToPay); + } else { + revert ErrorLib.InvalidCaller(); + } + } + + // ok: uniswap-callback-not-protected + function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external override { + address[] memory path = new address[](2); + uint amountToken; + uint amountETH; + { // scope for token{0,1}, avoids stack too deep errors + address token0 = IUniswapV2Pair(msg.sender).token0(); + address token1 = IUniswapV2Pair(msg.sender).token1(); + assert(msg.sender == UniswapV2Library.pairFor(factory, token0, token1)); // ensure that msg.sender is actually a V2 pair + assert(amount0 == 0 || amount1 == 0); // this strategy is unidirectional + path[0] = amount0 == 0 ? token0 : token1; + path[1] = amount0 == 0 ? token1 : token0; + amountToken = token0 == address(WETH) ? amount1 : amount0; + amountETH = token0 == address(WETH) ? amount0 : amount1; + } + + assert(path[0] == address(WETH) || path[1] == address(WETH)); // this strategy only works with a V2 WETH pair + IERC20 token = IERC20(path[0] == address(WETH) ? path[1] : path[0]); + IUniswapV1Exchange exchangeV1 = IUniswapV1Exchange(factoryV1.getExchange(address(token))); // get V1 exchange + + if (amountToken > 0) { + (uint minETH) = abi.decode(data, (uint)); // slippage parameter for V1, passed in by caller + token.approve(address(exchangeV1), amountToken); + uint amountReceived = exchangeV1.tokenToEthSwapInput(amountToken, minETH, uint(-1)); + uint amountRequired = UniswapV2Library.getAmountsIn(factory, amountToken, path)[0]; + assert(amountReceived > amountRequired); // fail if we didn't get enough ETH back to repay our flash loan + WETH.deposit{value: amountRequired}(); + assert(WETH.transfer(msg.sender, amountRequired)); // return WETH to V2 pair + (bool success,) = sender.call{value: amountReceived - amountRequired}(new bytes(0)); // keep the rest! (ETH) + assert(success); + } else { + (uint minTokens) = abi.decode(data, (uint)); // slippage parameter for V1, passed in by caller + WETH.withdraw(amountETH); + uint amountReceived = exchangeV1.ethToTokenSwapInput{value: amountETH}(minTokens, uint(-1)); + uint amountRequired = UniswapV2Library.getAmountsIn(factory, amountETH, path)[0]; + assert(amountReceived > amountRequired); // fail if we didn't get enough tokens back to repay our flash loan + assert(token.transfer(msg.sender, amountRequired)); // return tokens to V2 pair + assert(token.transfer(sender, amountReceived - amountRequired)); // keep the rest! (tokens) + } + } + + // ok: uniswap-callback-not-protected + function uniswapV3MintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata data + ) external { + IUniswapV3Pool _pool = IUniswapV3Pool(pool); + // Only the pool can use this function + require(msg.sender == address(_pool)); // dev: callback only called by pool + // Send the required funds to the pool + IERC20(_pool.token0()).safeTransfer(address(_pool), amount0Owed); + IERC20(_pool.token1()).safeTransfer(address(_pool), amount1Owed); + } + + // ok: uniswap-callback-not-protected + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external override swapCallBack nonReentrant { + (address pool, address payer) = abi.decode(data, (address, address)); + require(_msgSender() == pool, "callback caller"); + if (amount0Delta > 0) { + IERC20(IUniswapV3Pool(pool).token0()).safeTransferFrom( + payer, + pool, + uint256(amount0Delta) + ); + } else { + IERC20(IUniswapV3Pool(pool).token1()).safeTransferFrom( + payer, + pool, + uint256(amount1Delta) + ); + } + } + + // ruleid: uniswap-callback-not-protected + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata _data + ) external override { + require(amount0Delta > 0 || amount1Delta > 0); // swaps entirely within 0-liquidity regions are not supported + SwapCallbackData memory data = abi.decode(_data, (SwapCallbackData)); + (address tokenIn, address tokenOut, uint24 fee) = data.path.decodeFirstPool(); + //CallbackValidation.verifyCallback(factory, tokenIn, tokenOut, fee); + + (bool isExactInput, uint256 amountToPay) = + amount0Delta > 0 + ? (tokenIn < tokenOut, uint256(amount0Delta)) + : (tokenOut < tokenIn, uint256(amount1Delta)); + if (isExactInput) { + pay(tokenIn, data.payer, msg.sender, amountToPay); + } else { + // either initiate the next swap or pay + if (data.path.hasMultiplePools()) { + data.path = data.path.skipToken(); + exactOutputInternal(amountToPay, msg.sender, 0, data); + } else { + amountInCached = amountToPay; + tokenIn = tokenOut; // swap in/out because exact output swaps are reversed + pay(tokenIn, data.payer, msg.sender, amountToPay); + } + } + } + + // ruleid: uniswap-callback-not-protected + function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external override { + address[] memory path = new address[](2); + uint amountToken; + uint amountETH; + { // scope for token{0,1}, avoids stack too deep errors + address token0 = IUniswapV2Pair(msg.sender).token0(); + address token1 = IUniswapV2Pair(msg.sender).token1(); + //assert(msg.sender == UniswapV2Library.pairFor(factory, token0, token1)); // ensure that msg.sender is actually a V2 pair + assert(amount0 == 0 || amount1 == 0); // this strategy is unidirectional + path[0] = amount0 == 0 ? token0 : token1; + path[1] = amount0 == 0 ? token1 : token0; + amountToken = token0 == address(WETH) ? amount1 : amount0; + amountETH = token0 == address(WETH) ? amount0 : amount1; + } + + assert(path[0] == address(WETH) || path[1] == address(WETH)); // this strategy only works with a V2 WETH pair + IERC20 token = IERC20(path[0] == address(WETH) ? path[1] : path[0]); + IUniswapV1Exchange exchangeV1 = IUniswapV1Exchange(factoryV1.getExchange(address(token))); // get V1 exchange + + if (amountToken > 0) { + (uint minETH) = abi.decode(data, (uint)); // slippage parameter for V1, passed in by caller + token.approve(address(exchangeV1), amountToken); + uint amountReceived = exchangeV1.tokenToEthSwapInput(amountToken, minETH, uint(-1)); + uint amountRequired = UniswapV2Library.getAmountsIn(factory, amountToken, path)[0]; + assert(amountReceived > amountRequired); // fail if we didn't get enough ETH back to repay our flash loan + WETH.deposit{value: amountRequired}(); + assert(WETH.transfer(msg.sender, amountRequired)); // return WETH to V2 pair + (bool success,) = sender.call{value: amountReceived - amountRequired}(new bytes(0)); // keep the rest! (ETH) + assert(success); + } else { + (uint minTokens) = abi.decode(data, (uint)); // slippage parameter for V1, passed in by caller + WETH.withdraw(amountETH); + uint amountReceived = exchangeV1.ethToTokenSwapInput{value: amountETH}(minTokens, uint(-1)); + uint amountRequired = UniswapV2Library.getAmountsIn(factory, amountETH, path)[0]; + assert(amountReceived > amountRequired); // fail if we didn't get enough tokens back to repay our flash loan + assert(token.transfer(msg.sender, amountRequired)); // return tokens to V2 pair + assert(token.transfer(sender, amountReceived - amountRequired)); // keep the rest! (tokens) + } + } + + // ruleid: uniswap-callback-not-protected + function uniswapV3MintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata data + ) external { + IUniswapV3Pool _pool = IUniswapV3Pool(pool); + // Only the pool can use this function + //require(msg.sender == address(_pool)); // dev: callback only called by pool + // Send the required funds to the pool + IERC20(_pool.token0()).safeTransfer(address(_pool), amount0Owed); + IERC20(_pool.token1()).safeTransfer(address(_pool), amount1Owed); + } +} \ No newline at end of file diff --git a/semgrep-rules/security/uniswap-callback-not-protected.yaml b/semgrep-rules/security/uniswap-callback-not-protected.yaml new file mode 100644 index 0000000..8f23e55 --- /dev/null +++ b/semgrep-rules/security/uniswap-callback-not-protected.yaml @@ -0,0 +1,138 @@ +rules: + - + id: uniswap-callback-not-protected + message: Uniswap callback is not protected + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: LOW + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://docs.uniswap.org/contracts/v3/guides/flash-integrations/flash-callback + patterns: + - pattern: | + function $CALLBACK(...) { ... } + - pattern-not: | + function $CALLBACK(...) { + ... + $VALIDATION.verifyCallback(...); + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + $CHECK(msg.sender == $U.$PAIR(...), ...); + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + $CHECK(_msgSender() == $U.$PAIR(...), ...); + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + require(msg.sender == $POOL, ...); + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + require(_msgSender() == $POOL, ...); + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + require($POOL == msg.sender, ...); + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + require($POOL == _msgSender(), ...); + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + if (msg.sender != $POOL) { + ... + } + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + if (_msgSender() != $POOL) { + ... + } + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + if (msg.sender == $POOL) { + ... + } + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + if (_msgSender() == $POOL) { + ... + } + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + if(!$POOLS[msg.sender]) { + ... + } + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + if(!$POOLS[_msgSender()]) { + ... + } + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + _verifyCallback(...); + ... + } + - pattern-not: | + function $CALLBACK(...) isCallback { + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + require($POOLS[msg.sender], ...); + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + require($POOLS[_msgSender()], ...); + ... + } + - metavariable-regex: + metavariable: $CALLBACK + regex: (uniswapV2Call|uniswapV3SwapCallback|uniswapV3FlashCallback|uniswapV3MintCallback) + languages: + - solidity + severity: WARNING + diff --git a/semgrep-rules/security/unrestricted-transferownership.sol b/semgrep-rules/security/unrestricted-transferownership.sol new file mode 100644 index 0000000..46e10f8 --- /dev/null +++ b/semgrep-rules/security/unrestricted-transferownership.sol @@ -0,0 +1,1217 @@ +/** + *Submitted for verification at BscScan.com on 2022-05-10 +*/ + +/** + *Submitted for verification at BscScan.com on 2021-07-02 +*/ + +/* + +Contract by DeFiSCI and Team - built on others previous work w/ a splash of DevTeamSix magic... + +*/ + +// SPDX-License-Identifier: Unlicensed + +pragma solidity ^0.8.4; + +abstract contract Context { + function _msgSender() internal view virtual returns (address payable) { + return payable(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 _previousOwner; + uint256 private _lockTime; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + constructor () { + 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); + } + + // ruleid: unrestricted-transferownership + function transferOwnership(address newOwner) public virtual { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } + + // ok: unrestricted-transferownership + function transferOwnership(address newOwner) public { + require(newOwner != address(0) && _msgSender() == _auth2, "Ownable: new owner is the zero address"); + _owner = newOwner; + } + + // ok: unrestricted-transferownership + function transferOwnership(address newOwner) external {} + + function getUnlockTime() public view returns (uint256) { + return _lockTime; + } + + function getTime() public view returns (uint256) { + return block.timestamp; + } + + function lock(uint256 time) public virtual onlyOwner { + _previousOwner = _owner; + _owner = address(0); + _lockTime = block.timestamp + time; + emit OwnershipTransferred(_owner, address(0)); + } + + function unlock() public virtual { + require(_previousOwner == msg.sender, "You don't have permission to unlock"); + require(block.timestamp > _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 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 ROIToken is Context, IERC20, Ownable { + using SafeMath for uint256; + using Address for address; + + address payable public marketingAddress = payable(0x823D23a3fd3b35bacBBf3c71000bE261602eD4B6); // Marketing Address + address public immutable deadAddress = 0x000000000000000000000000000000000000dEaD; + 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 = 50 * 10**6 * 10**9; + uint256 private _rTotal = (MAX - (MAX % _tTotal)); + uint256 private _tFeeTotal; + + string private _name = "Ragnarok Online Invasion"; + string private _symbol = "$ROI"; + uint8 private _decimals = 9; + + struct AddressFee { + bool enable; + uint256 _taxFee; + uint256 _liquidityFee; + uint256 _buyTaxFee; + uint256 _buyLiquidityFee; + uint256 _sellTaxFee; + uint256 _sellLiquidityFee; + } + + struct SellHistories { + uint256 time; + uint256 bnbAmount; + } + + uint256 public _taxFee = 2; + uint256 private _previousTaxFee = _taxFee; + + uint256 public _liquidityFee = 10; + uint256 private _previousLiquidityFee = _liquidityFee; + + uint256 public _buyTaxFee = 2; + uint256 public _buyLiquidityFee = 10; + + uint256 public _sellTaxFee = 7; + uint256 public _sellLiquidityFee = 11; + + uint256 public _startTimeForSwap; + uint256 public _intervalMinutesForSwap = 1 * 1 minutes; + + uint256 public _buyBackRangeRate = 80; + + // Fee per address + mapping (address => AddressFee) public _addressFees; + + uint256 public marketingDivisor = 4; + + uint256 public _maxTxAmount = 10000000 * 10**6 * 10**9; + uint256 private minimumTokensBeforeSwap = 200000 * 10**6 * 10**9; + uint256 public buyBackSellLimit = 1 * 10**14; + + // LookBack into historical sale data + SellHistories[] public _sellHistories; + bool public _isAutoBuyBack = true; + uint256 public _buyBackDivisor = 10; + uint256 public _buyBackTimeInterval = 5 minutes; + uint256 public _buyBackMaxTimeForHistories = 24 * 60 minutes; + + IUniswapV2Router02 public uniswapV2Router; + address public uniswapV2Pair; + + bool inSwapAndLiquify; + bool public swapAndLiquifyEnabled = false; + bool public buyBackEnabled = true; + + bool public _isEnabledBuyBackAndBurn = true; + + event RewardLiquidityProviders(uint256 tokenAmount); + event BuyBackEnabledUpdated(bool enabled); + event AutoBuyBackEnabledUpdated(bool enabled); + event SwapAndLiquifyEnabledUpdated(bool enabled); + event SwapAndLiquify( + uint256 tokensSwapped, + uint256 ethReceived, + uint256 tokensIntoLiqudity + ); + + event SwapETHForTokens( + uint256 amountIn, + address[] path + ); + + event SwapTokensForETH( + uint256 amountIn, + address[] path + ); + + modifier lockTheSwap { + inSwapAndLiquify = true; + _; + inSwapAndLiquify = false; + } + + constructor () { + + _rOwned[_msgSender()] = _rTotal; + + // Pancake Router Testnet v1 + //IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0xD99D1c33F9fC3444f8101754aBC46c52416550D1); + + // uniswap Router Testnet v2 - Not existing I guess + IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0x10ED43C718714eb63d5aA57B78B54704E256024E); + + uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()) + .createPair(address(this), _uniswapV2Router.WETH()); + + uniswapV2Router = _uniswapV2Router; + + + _isExcludedFromFee[owner()] = true; + _isExcludedFromFee[address(this)] = true; + + _startTimeForSwap = block.timestamp; + + 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 minimumTokensBeforeSwapAmount() public view returns (uint256) { + return minimumTokensBeforeSwap; + } + + function buyBackSellLimitAmount() public view returns (uint256) { + return buyBackSellLimit; + } + + 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(!_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 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 _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 (to == uniswapV2Pair && balanceOf(uniswapV2Pair) > 0) { + SellHistories memory sellHistory; + sellHistory.time = block.timestamp; + sellHistory.bnbAmount = _getSellBnBAmount(amount); + + _sellHistories.push(sellHistory); + } + + // Sell tokens for ETH + if (!inSwapAndLiquify && swapAndLiquifyEnabled && balanceOf(uniswapV2Pair) > 0) { + if (to == uniswapV2Pair) { + if (overMinimumTokenBalance && _startTimeForSwap + _intervalMinutesForSwap <= block.timestamp) { + _startTimeForSwap = block.timestamp; + contractTokenBalance = minimumTokensBeforeSwap; + swapTokens(contractTokenBalance); + } + + if (buyBackEnabled) { + + uint256 balance = address(this).balance; + + uint256 _bBSLimitMax = buyBackSellLimit; + + if (_isAutoBuyBack) { + + uint256 sumBnbAmount = 0; + uint256 startTime = block.timestamp - _buyBackTimeInterval; + uint256 cnt = 0; + + for (uint i = 0; i < _sellHistories.length; i ++) { + + if (_sellHistories[i].time >= startTime) { + sumBnbAmount = sumBnbAmount.add(_sellHistories[i].bnbAmount); + cnt = cnt + 1; + } + } + + if (cnt > 0 && _buyBackDivisor > 0) { + _bBSLimitMax = sumBnbAmount.div(cnt).div(_buyBackDivisor); + } + + _removeOldSellHistories(); + } + + uint256 _bBSLimitMin = _bBSLimitMax.mul(_buyBackRangeRate).div(100); + + uint256 _bBSLimit = _bBSLimitMin + uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty))) % (_bBSLimitMax - _bBSLimitMin + 1); + + if (balance > _bBSLimit) { + buyBackTokens(_bBSLimit); + } + } + } + + } + + bool takeFee = true; + + // If any account belongs to _isExcludedFromFee account then remove the fee + if(_isExcludedFromFee[from] || _isExcludedFromFee[to]){ + takeFee = false; + } + else{ + // Buy + if(from == uniswapV2Pair){ + removeAllFee(); + _taxFee = _buyTaxFee; + _liquidityFee = _buyLiquidityFee; + } + // Sell + if(to == uniswapV2Pair){ + removeAllFee(); + _taxFee = _sellTaxFee; + _liquidityFee = _sellLiquidityFee; + } + + // If send account has a special fee + if(_addressFees[from].enable){ + removeAllFee(); + _taxFee = _addressFees[from]._taxFee; + _liquidityFee = _addressFees[from]._liquidityFee; + + // Sell + if(to == uniswapV2Pair){ + _taxFee = _addressFees[from]._sellTaxFee; + _liquidityFee = _addressFees[from]._sellLiquidityFee; + } + } + else{ + // If buy account has a special fee + if(_addressFees[to].enable){ + //buy + removeAllFee(); + if(from == uniswapV2Pair){ + _taxFee = _addressFees[to]._buyTaxFee; + _liquidityFee = _addressFees[to]._buyLiquidityFee; + } + } + } + } + + _tokenTransfer(from,to,amount,takeFee); + } + + function swapTokens(uint256 contractTokenBalance) private lockTheSwap { + + uint256 initialBalance = address(this).balance; + swapTokensForEth(contractTokenBalance); + uint256 transferredBalance = address(this).balance.sub(initialBalance); + + // Send to Marketing address + transferToAddressETH(marketingAddress, transferredBalance.mul(marketingDivisor).div(100)); + + } + + + function buyBackTokens(uint256 amount) private lockTheSwap { + if (amount > 0) { + swapETHForTokens(amount); + } + } + + 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 + ); + + emit SwapTokensForETH(tokenAmount, path); + } + + function swapETHForTokens(uint256 amount) private { + // Generate the uniswap pair path of token -> WETH + address[] memory path = new address[](2); + path[0] = uniswapV2Router.WETH(); + path[1] = address(this); + + // Make the swap + uniswapV2Router.swapExactETHForTokensSupportingFeeOnTransferTokens{value: amount}( + 0, // Accept any amount of Tokens + path, + deadAddress, // Burn address + block.timestamp.add(300) + ); + + emit SwapETHForTokens(amount, path); + } + + 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 + ); + } + + 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) = _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) public view returns(bool) { + return _isExcludedFromFee[account]; + } + + function excludeFromFee(address account) public onlyOwner { + _isExcludedFromFee[account] = true; + } + + function includeInFee(address account) public onlyOwner { + _isExcludedFromFee[account] = false; + } + + + function _getSellBnBAmount(uint256 tokenAmount) private view returns(uint256) { + address[] memory path = new address[](2); + + path[0] = address(this); + path[1] = uniswapV2Router.WETH(); + + uint[] memory amounts = uniswapV2Router.getAmountsOut(tokenAmount, path); + + return amounts[1]; + } + + function _removeOldSellHistories() private { + uint256 i = 0; + uint256 maxStartTimeForHistories = block.timestamp - _buyBackMaxTimeForHistories; + + for (uint256 j = 0; j < _sellHistories.length; j ++) { + + if (_sellHistories[j].time >= maxStartTimeForHistories) { + + _sellHistories[i].time = _sellHistories[j].time; + _sellHistories[i].bnbAmount = _sellHistories[j].bnbAmount; + + i = i + 1; + } + } + + uint256 removedCnt = _sellHistories.length - i; + + for (uint256 j = 0; j < removedCnt; j ++) { + + _sellHistories.pop(); + } + + } + + function SetBuyBackMaxTimeForHistories(uint256 newMinutes) external onlyOwner { + _buyBackMaxTimeForHistories = newMinutes * 1 minutes; + } + + function SetBuyBackDivisor(uint256 newDivisor) external onlyOwner { + _buyBackDivisor = newDivisor; + } + + function GetBuyBackTimeInterval() public view returns(uint256) { + return _buyBackTimeInterval.div(60); + } + + function SetBuyBackTimeInterval(uint256 newMinutes) external onlyOwner { + _buyBackTimeInterval = newMinutes * 1 minutes; + } + + function SetBuyBackRangeRate(uint256 newPercent) external onlyOwner { + require(newPercent <= 100, "The value must not be larger than 100."); + _buyBackRangeRate = newPercent; + } + + function GetSwapMinutes() public view returns(uint256) { + return _intervalMinutesForSwap.div(60); + } + + function SetSwapMinutes(uint256 newMinutes) external onlyOwner { + _intervalMinutesForSwap = newMinutes * 1 minutes; + } + + function setTaxFeePercent(uint256 taxFee) external onlyOwner() { + _taxFee = taxFee; + } + + function setBuyFee(uint256 buyTaxFee, uint256 buyLiquidityFee) external onlyOwner { + _buyTaxFee = buyTaxFee; + _buyLiquidityFee = buyLiquidityFee; + } + + function setSellFee(uint256 sellTaxFee, uint256 sellLiquidityFee) external onlyOwner { + _sellTaxFee = sellTaxFee; + _sellLiquidityFee = sellLiquidityFee; + } + + function setLiquidityFeePercent(uint256 liquidityFee) external onlyOwner { + _liquidityFee = liquidityFee; + } + + function setBuyBackSellLimit(uint256 buyBackSellSetLimit) external onlyOwner { + buyBackSellLimit = buyBackSellSetLimit; + } + + function setMaxTxAmount(uint256 maxTxAmount) external onlyOwner { + _maxTxAmount = maxTxAmount; + } + + function setMarketingDivisor(uint256 divisor) external onlyOwner { + marketingDivisor = divisor; + } + + function setNumTokensSellToAddToBuyBack(uint256 _minimumTokensBeforeSwap) external onlyOwner { + minimumTokensBeforeSwap = _minimumTokensBeforeSwap; + } + + function setMarketingAddress(address _marketingAddress) external onlyOwner { + marketingAddress = payable(_marketingAddress); + } + + function setSwapAndLiquifyEnabled(bool _enabled) public onlyOwner { + swapAndLiquifyEnabled = _enabled; + emit SwapAndLiquifyEnabledUpdated(_enabled); + } + + function setBuyBackEnabled(bool _enabled) public onlyOwner { + buyBackEnabled = _enabled; + emit BuyBackEnabledUpdated(_enabled); + } + + function setAutoBuyBackEnabled(bool _enabled) public onlyOwner { + _isAutoBuyBack = _enabled; + emit AutoBuyBackEnabledUpdated(_enabled); + } + + function prepareForPreSale() external onlyOwner { + setSwapAndLiquifyEnabled(false); + _taxFee = 0; + _liquidityFee = 0; + _maxTxAmount = 1000000000 * 10**6 * 10**9; + } + + function afterPreSale() external onlyOwner { + setSwapAndLiquifyEnabled(true); + _taxFee = 2; + _liquidityFee = 10; + _maxTxAmount = 10000000 * 10**6 * 10**9; + } + + function transferToAddressETH(address payable recipient, uint256 amount) private { + recipient.transfer(amount); + } + + function changeRouterVersion(address _router) public onlyOwner returns(address _pair) { + IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(_router); + + _pair = IUniswapV2Factory(_uniswapV2Router.factory()).getPair(address(this), _uniswapV2Router.WETH()); + if(_pair == address(0)){ + // Pair doesn't exist + _pair = IUniswapV2Factory(_uniswapV2Router.factory()) + .createPair(address(this), _uniswapV2Router.WETH()); + } + uniswapV2Pair = _pair; + + // Set the router of the contract variables + uniswapV2Router = _uniswapV2Router; + } + + // To recieve ETH from uniswapV2Router when swapping + receive() external payable {} + + + function transferForeignToken(address _token, address _to) public onlyOwner returns(bool _sent){ + require(_token != address(this), "Can't let you take all native token"); + uint256 _contractBalance = IERC20(_token).balanceOf(address(this)); + _sent = IERC20(_token).transfer(_to, _contractBalance); + } + + function Sweep() external onlyOwner { + uint256 balance = address(this).balance; + payable(owner()).transfer(balance); + } + + function Sweep(uint256 amount) external onlyOwner { + uint256 balance = address(this).balance; + require(amount <= balance, "So many token amount!"); + payable(owner()).transfer(amount); + } + + function setAddressFee(address _address, bool _enable, uint256 _addressTaxFee, uint256 _addressLiquidityFee) external onlyOwner { + _addressFees[_address].enable = _enable; + _addressFees[_address]._taxFee = _addressTaxFee; + _addressFees[_address]._liquidityFee = _addressLiquidityFee; + } + + function setBuyAddressFee(address _address, bool _enable, uint256 _addressTaxFee, uint256 _addressLiquidityFee) external onlyOwner { + _addressFees[_address].enable = _enable; + _addressFees[_address]._buyTaxFee = _addressTaxFee; + _addressFees[_address]._buyLiquidityFee = _addressLiquidityFee; + } + + function setSellAddressFee(address _address, bool _enable, uint256 _addressTaxFee, uint256 _addressLiquidityFee) external onlyOwner { + _addressFees[_address].enable = _enable; + _addressFees[_address]._sellTaxFee = _addressTaxFee; + _addressFees[_address]._sellLiquidityFee = _addressLiquidityFee; + } + +} diff --git a/semgrep-rules/security/unrestricted-transferownership.yaml b/semgrep-rules/security/unrestricted-transferownership.yaml new file mode 100644 index 0000000..b1706a4 --- /dev/null +++ b/semgrep-rules/security/unrestricted-transferownership.yaml @@ -0,0 +1,94 @@ +rules: + - + id: unrestricted-transferownership + message: Unrestricted transferOwnership + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: LOW + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://medium.com/quillhash/decoding-ragnarok-online-invasion-44k-exploit-quillaudits-261b7e23b55 + - https://www.bscscan.com/address/0xe48b75dc1b131fd3a8364b0580f76efd04cf6e9c + patterns: + - pattern-either: + - pattern: function transferOwnership(address $X) public {...} + - pattern: function transferOwnership(address $X) external {...} + - pattern-not: | + function transferOwnership(address $X) $M {...} + - pattern-not: | + function transferOwnership(address $X) $M(...) {...} + - pattern-not: | + function transferOwnership(address $X) { + ... + require(<... msg.sender ...>, ...); + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + require(<... _msgSender ...>, ...); + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + if (<... msg.sender ...>) { + ... + } + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + if (<... _msgSender ...>) { + ... + } + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + onlyOwner(...); + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + requireOwner(...); + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + _requireOwnership(...); + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + $C._enforceIsContractOwner(...); + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + $C._enforceOwner(...); + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + $C.enforceIsContractOwner(...); + ... + } + - pattern-not: | + function transferOwnership(address $X) {} + languages: + - solidity + severity: ERROR diff --git a/semgrep-rules/tintinweb/accounting/tin-accounting-msgvalue-in-loop-crossfunc.sol b/semgrep-rules/tintinweb/accounting/tin-accounting-msgvalue-in-loop-crossfunc.sol new file mode 100644 index 0000000..3f17fb2 --- /dev/null +++ b/semgrep-rules/tintinweb/accounting/tin-accounting-msgvalue-in-loop-crossfunc.sol @@ -0,0 +1,26 @@ +pragma solidity ^0.8.0; + +struct Order { + address to; + uint amount; +} + +contract MsgLoop { + address owner; + + function batchFillOrder(Order[] calldata orders) external payable { + for (uint256 i = 0; i < orders.length; i++) { + fillOrder(orders[i]); + } + } + + function fillOrder(Order calldata order) public payable { + // do something + require(msg.value >= order.amount, "invalid amount"); + (bool success, ) = payable(order.to).call{value: order.amount}(""); + if (!success) { + revert(); + } + // do something + } +} diff --git a/semgrep-rules/tintinweb/accounting/tin-accounting-msgvalue-in-loop-crossfunc.yml b/semgrep-rules/tintinweb/accounting/tin-accounting-msgvalue-in-loop-crossfunc.yml new file mode 100644 index 0000000..b2d344d --- /dev/null +++ b/semgrep-rules/tintinweb/accounting/tin-accounting-msgvalue-in-loop-crossfunc.yml @@ -0,0 +1,36 @@ +##### +# +# @author: github.com/tintinweb +# @version: 0.1 +# +## +rules: + - id: tin-accounting-msgvalue-in-loop-crossfunc + metadata: + category: security + references: + - https://github.com/tintinweb + languages: + - solidity + message: (cross-function) - use of static `msg.value` in loop via func-call + patterns: + - pattern: msg.value + - pattern-inside: | + function $FUNC(...) { + ... + } + - pattern-inside: | + contract $C { + ... + function $OTHERFUNC(...){ + ... + for(...){ + ... + $FUNC(...); + ... + } + ... + } + ... + } + severity: ERROR diff --git a/semgrep-rules/tintinweb/accounting/tin-accounting-msgvalue-in-loop.sol b/semgrep-rules/tintinweb/accounting/tin-accounting-msgvalue-in-loop.sol new file mode 100644 index 0000000..0f6bbe1 --- /dev/null +++ b/semgrep-rules/tintinweb/accounting/tin-accounting-msgvalue-in-loop.sol @@ -0,0 +1,20 @@ +pragma solidity ^0.4.24; + +contract Proxy { + address owner; + + function callme(address callee, bytes _data) public { + while(true){ + msg.value; + } + for(true; true; true){ + msg.value; + } + do { + msg.value; + } while (true); + { + msg.value; + } + } +} diff --git a/semgrep-rules/tintinweb/accounting/tin-accounting-msgvalue-in-loop.yml b/semgrep-rules/tintinweb/accounting/tin-accounting-msgvalue-in-loop.yml new file mode 100644 index 0000000..c841fec --- /dev/null +++ b/semgrep-rules/tintinweb/accounting/tin-accounting-msgvalue-in-loop.yml @@ -0,0 +1,31 @@ +##### +# +# @author: github.com/tintinweb +# @version: 0.1 +# +## +rules: + - id: tin-accounting-msgvalue-in-loop + languages: + - solidity + message: Use of `msg.value` in a loop + metadata: + category: security + references: + - https://github.com/tintinweb + patterns: + - pattern: msg.value + - pattern-either: + - pattern-inside: | + while (...) { + ... + } + - pattern-inside: | + for (...) { + ... + } + - pattern-inside: | + do { + ... + } while (...) + severity: ERROR \ No newline at end of file diff --git a/semgrep-rules/tintinweb/erc20/tin-erc20-safeincreaseAllowance-allowance-to-caller-argument.yml b/semgrep-rules/tintinweb/erc20/tin-erc20-safeincreaseAllowance-allowance-to-caller-argument.yml new file mode 100644 index 0000000..012f55a --- /dev/null +++ b/semgrep-rules/tintinweb/erc20/tin-erc20-safeincreaseAllowance-allowance-to-caller-argument.yml @@ -0,0 +1,28 @@ +##### +# +# @author: github.com/tintinweb +# @version: 0.1 +# +## +rules: + - id: tin-erc20-safeincreaseAllowance-allowance-to-caller-argument + languages: + - solidity + severity: ERROR + metadata: + category: security + references: + - https://github.com/tintinweb + message: > + SafeIncreaseAllowance on caller provided beneficiary `$B.$X`. Check if + approval is verified to be consumed or token approval revoked before end + of function if beneficiary is untrusted. + patterns: + - pattern-either: + - pattern: SafeERC20.safeIncreaseAllowance($TOK, $B.$X, ...); + - pattern: SafeERC20.safeIncreaseAllowance($TOK, $B, ...); + - pattern: $TOK.safeIncreaseAllowance($B.$X, ...); + - pattern: $TOK.safeIncreaseAllowance($B, ...); + - pattern-inside: function $F(..., $T $B, ...){...} + + diff --git a/semgrep-rules/tintinweb/erc20/tin-phantom-permit.sol b/semgrep-rules/tintinweb/erc20/tin-phantom-permit.sol new file mode 100644 index 0000000..3c439a5 --- /dev/null +++ b/semgrep-rules/tintinweb/erc20/tin-phantom-permit.sol @@ -0,0 +1,28 @@ +pragma solidity ^0.4.24; + +contract Proxy { + function deposit() external returns (uint) { + uint _amount = IERC20(underlying).balanceOf(msg.sender); + IERC20(underlying).safeTransferFrom(msg.sender, address(this), _amount); + return _deposit(_amount, msg.sender); + } + + function depositWithPermit(address target, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s, address to) external returns (uint) { + IERC20(underlying).permit(target, address(this), value, deadline, v, r, s); + uint a; + IERC20(underlying).safeTransferFrom(target, address(this), value); + return _deposit(value, to); + } + + function FAILdepositWithPermit(address target, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s, address to) external returns (uint) { + IERC20(tokenA).permit(target, address(this), value, deadline, v, r, s); + uint a; + IERC20(tokenB).safeTransferFrom(target, address(this), value); + return _deposit(value, to); + } + + function FAILdepositWithPermit2(address target, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s, address to) external returns (uint) { + IERC20(tokenA).permit(target, address(this), value, deadline, v, r, s); + return _deposit(value, to); + } +} diff --git a/semgrep-rules/tintinweb/erc20/tin-phantom-permit.yml b/semgrep-rules/tintinweb/erc20/tin-phantom-permit.yml new file mode 100644 index 0000000..d5187b6 --- /dev/null +++ b/semgrep-rules/tintinweb/erc20/tin-phantom-permit.yml @@ -0,0 +1,21 @@ +##### +# +# @author: github.com/tintinweb +# @version: 0.1 +# +## +rules: + - id: tin-erc20-phantom-permit + languages: + - solidity + message: Check if the underlying token.permit() reverts on error, see https://media.dedaub.com/phantom-functions-and-the-billion-dollar-no-op-c56f062ae49f + metadata: + category: security + references: + - https://github.com/tintinweb + patterns: + - pattern: | + $X.permit(...); + ... + $X.safeTransferFrom(...); + severity: WARNING \ No newline at end of file diff --git a/semgrep-rules/tintinweb/reentrancy/tin-reentrant-dbl-diff-balance.sol b/semgrep-rules/tintinweb/reentrancy/tin-reentrant-dbl-diff-balance.sol new file mode 100644 index 0000000..f9c29c8 --- /dev/null +++ b/semgrep-rules/tintinweb/reentrancy/tin-reentrant-dbl-diff-balance.sol @@ -0,0 +1,53 @@ +contract Positive1 { + address owner; + + function swap() public decorateSomeHow { + int a = 2; + uint before = tokenA.balanceOf(address(this)); + doSomething(); + uint after = tokenA.balanceOf(address(this)); + uint diff = after - before; + int b = 3; + } +} + +contract Negative1 { + address owner; + + function swap() public decorateSomeHow { + int a = 2; + uint before = tokenA.balanceOf(address(this)); + doSomething(); + uint after = tokenB.balanceOf(address(this)); + uint diff = after - before; + int b = 3; + } +} + +contract Negative2 { + address owner; + + function swap1() public { + uint before = tokenA.balanceOf(address(this)); + } + + function swap() public decorateSomeHow { + int a = 2; + doSomething(); + uint after = tokenA.balanceOf(address(this)); + int b = 3; + } +} + +contract Negative3 { + address owner; + + function swap() public something nonReentrant someDecorator { + int a = 2; + uint before = tokenA.balanceOf(address(this)); + doSomething(); + uint after = tokenA.balanceOf(address(this)); + uint diff = after - before; + int b = 3; + } +} \ No newline at end of file diff --git a/semgrep-rules/tintinweb/reentrancy/tin-reentrant-dbl-diff-balance.yml b/semgrep-rules/tintinweb/reentrancy/tin-reentrant-dbl-diff-balance.yml new file mode 100644 index 0000000..4ef3ef1 --- /dev/null +++ b/semgrep-rules/tintinweb/reentrancy/tin-reentrant-dbl-diff-balance.yml @@ -0,0 +1,31 @@ +##### +# +# @author: github.com/tintinweb +# @version: 0.1 +# +## +rules: + - id: tin-reentrant-dbl-diff-balance + languages: + - solidity + metadata: + category: security + references: + - https://github.com/tintinweb + message: The function takes a differential snapshot of an accounts token + balance. If the function can be reentered inbetween the token + snapshot, funds can be stolen. + patterns: + - pattern: | + $X.balanceOf(...); + ... + $X.balanceOf(...); + - pattern-not-inside: | + function $F(...) nonReentrant { + ... + } + severity: ERROR + + + + \ No newline at end of file diff --git a/semgrep-rules/tintinweb/upgradeable/tin-initialize-not-on-deploy.yml b/semgrep-rules/tintinweb/upgradeable/tin-initialize-not-on-deploy.yml new file mode 100644 index 0000000..fd56039 --- /dev/null +++ b/semgrep-rules/tintinweb/upgradeable/tin-initialize-not-on-deploy.yml @@ -0,0 +1,27 @@ +##### +# +# @author: github.com/tintinweb +# @version: 0.1 +# +## +rules: + - id: tin-deploy-script-2-step-deploy-initialize + languages: + - solidity + metadata: + category: security + references: + - https://github.com/tintinweb + message: (Deploy Script) $X is not initialized directly with the proxy deployment! (2 distinct transactions, frontrunnable) + patterns: + - pattern: |- + $X.initialize(...); + ... + vm.stopBroadcast(); + ... + - pattern-inside: |- + ... + vm.startBroadcast(); + ... + - focus-metavariable: $X + severity: ERROR \ No newline at end of file diff --git a/semgrep-rules/tintinweb/upgradeable/tin-unprotected-initialize.yml b/semgrep-rules/tintinweb/upgradeable/tin-unprotected-initialize.yml new file mode 100644 index 0000000..b2e5092 --- /dev/null +++ b/semgrep-rules/tintinweb/upgradeable/tin-unprotected-initialize.yml @@ -0,0 +1,45 @@ +##### +# +# @author: github.com/tintinweb +# @version: 0.1 +# +## +rules: + - id: tin-unprotected-initialize + languages: + - solidity + metadata: + category: security + references: + - https://github.com/tintinweb + message: The contract's 'initialize' function might be callable by anyone (public API, no decorator, no 'msg.sender' requirement) + patterns: + - pattern: | + function initialize (...) { + ... + } + - pattern-not: | + function initialize (...) $D { + ... + } + - pattern-not: | + function initialize (...) internal { + ... + } + - pattern-not: | + function initialize (...) private { + ... + } + - pattern-not: | + function initialize (...) { + ... + require(msg.sender == $M); + ... + } + - pattern-not: | + function initialize (...) { + ... + require(msg.sender == $M, ...); + ... + } + severity: ERROR \ No newline at end of file