# BBB:Unsecure.sol audit ## Summary: [H-01] No one can be an OWNER. ### Vulnerability Detail `address owner = msg.sender` defines msg.sender to be owner, but no one can be owner because `constructor` is not used ### Impact Cannot set functions that only the owner can execute. ### Code Snippet ```solidity 13 address owner = msg.sender; ``` ### Tool used Manual Review ### Recommendation Set the owner with `constructor` ```solidity constructor { address owner = msg.sender; } ``` If the owner is not set, it is very dangerous because the function of the owner will be executed by someone who is not the owner. So the danger level is set to 'high'. ## Summary: [H-02] Cannot execute anyone `function addApprovedTokens` because it is private ### Vulnerability Detail The function is set as public that can be executed by the owner, but no one can execute it because it is private. ### Impact No one can set the white list token if this function is private. ### Code Snippet ```solidity 41 function addApprovedTokens(address _token) private { 42 if (msg.sender != owner) revert(); 43 approvedTokens.push(_token); 44 } ``` ### Tool used Manual Review ### Recommendation Change `private` to `public` ## Summary: [H-03] User will not receive rewards for previous deposits. ### Vulnerability Detail If a user repeats a deposit more than twice in a row before executing a withdrawal, the deposit will be overwritten and the user will only receive the last deposit of the Reward. Also, the previous amount of reward will not be received. ### Impact The user wouldn't get the Reward that the user suppose to obtain. ### Code Snippet ```solidity function deposit(uint _amount, address _token, bool _isETH) public { if (!_isXXX(_token, whitelist)) revert(); DepostInfo memory depositInfo; TransferInfo memory info = TransferInfo({ isETH: _isETH, token: _token; from: msg.sender, amount: _amount, to: address(this), }); _tokenTransfer(info); depositInfo.lastTime = uint40(block.timestamp); depositInfo.amount = _amount; depositAmt[msg.sender][_token] = depositInfo; } ``` If this function deposit is executed twice in a row, the deposit is overwritten. Also, the function withdraw get reward executed is calculated by referring to the overwritten depositAmt, so only the one based on the last deposit record can get the Reward. ```solidity function getReward(address token) public view returns (uint reward) { uint amount = depositAmt[msg.sender][token].amount; uint lastTime = depositAmt[msg.sender][token].lastTime; reward = (REWARD_RATE / (block.timestamp - lastTime)) * amount; } ``` ### Tool used Manual Review ### Recommendation Change `depositAmt` to be able to add to `amount`. ```solidity 95 depositInfo.amount = depositAmt[msg.sender][_token].amount + _amount; ``` ## Summary: [H-04] User can withdraw more than the deposited amount. And users can earn Reward as many times as they want. ### Vulnerability Detail Amount: Since the value of `depositAmt[msg.sender][token].amout` is not updated in the withdraw function, the User can continue to withdraw as long as the `token` specified in this contract remains. Reward: If the User enters 0 for the `amount` argument and withdraws it, the `Reward` can be obtained many times without withdrawing the `amount`, as long as the `tokenBBB` remains. This is because the value of `depositAmt`, which is also used in `getReward`, is not updated. ### Impact e.g. Amount: 1. This contract has 100USDC. Eve has deposited 10USDC. 2. Eve executes `function withdraw(_amout(10),_token(USDC))`. 3. Eve withdraws 10 USDC and also earns a calculated Reward. 4. However, since `depositAmt[msg.sender][_token].amount` is not updated, Eve can withdraw again and again until the USDC owned by this contract is reached. Reward: 1. Eve enter 0 for amaunt,token enter something. 2. The `amount` is not withdrawn, but the Reward is calculated and the BBBtoken can be earned. ### Code Snippet ```solidity=99 function withdraw( address _to, uint _amount, bool _isETH, address _token ) public { if (!_isXXX(_token, whitelist)) revert(); TransferInfo memory info = TransferInfo({ isETH: _isETH, token: _token; from: address(this), amount: _amount, to: _to, }); uint canWithdrawAmount = depositAmt[msg.sender][_token].amount; require(_info.amount < canWithdrawAmount, "ERROR"); canWithdrawAmount = 0; _tokenTransfer(_info); uint rewardAmount = getReward(_token); IERC20(BBBToken).transfer(msg.sender, rewardAmount); } ``` ### Tool used Manual Review ### Recommendation Added confirmation that `amount` is non-zero Instead of updating `canWithdrawAmount`, update `depositAmt[msg.sender][_token].amount`. ```solidity=113 uint canWithdrawAmount = depositAmt[msg.sender][_token].amount; require(_info.amount, "ERROR"); // throw error when '_amount' is zero require(_info.amount < canWithdrawAmount, "ERROR"); uint rewardAmount = getReward(_token); depositAmt[msg.sender][_token].amount -= _info.amount; // update deposited amount _tokenTransfer(_info); IERC20(BBBToken).transfer(msg.sender, rewardAmount); ``` Note:Change the order of line `113` to `119` to follow the C-E-I pattern. ## Summary: [M-01] No initial value set for argument `i` ### Vulnerability Detail `function _isXXX` checks if the address array `_xxx` contains the address `_token`. However, this function fails because the initial value is not set. ### Impact ### Code Snippet ```solidity=64 for (uint i; i < length; ) { if (_token == _xxx[i]) return true; unchecked { ++i; } ``` ### Tool used Manual Review ### Recommendation Set initial values for `i` ```solidity=64 for (uint i = 0; i < length; ) { if (_token == _xxx[i]) return true; unchecked { ++i; } ``` ## Summary: [M-02] User can deposit with reentrancy and such deposit is locked in this contract forever. ### Vulnerability Detail As same as the issue H-04, the order of token deposit does not follow the C-F-I pattern. Because of that, user can reentrance and deposit tokens, and deposited tokens via reentrancing is not reflected to the usre's deposit info, so they are not able to withdraw such deposit. ### Impact User's deposit via reentrance gets locked to this contract. ### Tool used Manual Review ### Recommendation Fix the order of line `93` to `96` to follow the C-E-I pattern. #### Before ```solidity=93 _tokenTransfer(info); depositInfo.lastTime = uint40(block.timestamp); depositInfo.amount = _amount; depositAmt[msg.sender][_token] = depositInfo; ``` #### After ```solidity=93 depositInfo.lastTime = uint40(block.timestamp); depositInfo.amount = _amount; depositAmt[msg.sender][_token] = depositInfo; _tokenTransfer(info); ``` ## Summary: [M-03] Possibly overflowing due to time calculations. ### Vulnerability Detail If you calculate "time" using value uint40, there is a potential for overflow. For example, time * 2 will be stored into uint40, while the result might be larger than 40 bits. For this purpose only, it is already better to just keep the stuff in uint256s. As to gas cost, keeping all your data aligned to 256 bits, will actually yield lower gas-cost. ### Impact Being at risk of getting an overflow in one of your calculations, as a result of the compiler choosing the shortest type which is sufficient for the operation. ### Code Snippet ```solidity // line of 94 depositInfo.lastTime = uint40(block.timestamp); ``` ```solidity // line of 56 reward = (REWARD_RATE / (block.timestamp - lastTime)) * amount; ``` ### Tool used none ### Recommendation don't declare uint40 for "time" but use uint256 #### Before ```solidity // line of 94 depositInfo.lastTime = uint40 ``` #### After ```solidity // line of 94 depositInfo.lastTime = uint256(block.timestamp); ``` ## Summary: [M-04] Validation Errors ### Vulnerability Detail The validation in the `withdraw` function is to check if the argument `token` is in the whitelist. The `withdraw` function should check if `msg.sender` has ever `deposited` with this `token`. ### Impact Even if the function is on the white list, it is not checked if the `msg.sender` that executes the function is a `token` that has been `deposit`. ### Code Snippet ```solidity=105 if (!_isXXX(_token, whitelist)) revert(); ``` ### Tool used Manual Review ### Recommendation Add this validation. ```solidity=105 if (!_isXXX(_token, whitelist)) revert(); if (!depositAmt[msg.sender][_token]) revert(); ``` ## Summary: [M-05] Reward calculation is incorrect. ### Vulnerability Detail The calculation is that the longer a user is on deposit, the less the reward. Could this be a mistake in the formula? ### Impact e.g. A: REWARD_RATE = 50 block.timestamp = 2022 lastTime = 2021 amount = 100 __reward = 500__ B: REWARD_RATE = 50 block.timestamp = 2023 lastTime = 2021 amount = 100 __reward = 250__ B has been deposited for a longer period of time, but A, who has been deposited for a shorter period of time, is more remunerated. This eliminates the need to deposit for a longer period of time. ### Code Snippet ```soliddi=56 reward = (REWARD_RATE / (block.timestamp - lastTime)) * amount; ``` ### Tool used Manual Review ### Recommendation ```solidity=56 reward = (block.timestarmp- lastTime) * amount * (REWARD_RATE / 100) // Divide by 100 because REWARD_RATE is the reward rate. ``` A: REWARD_RATE = 50 block.timestamp = 2022 lastTime = 2021 amount = 100 __reward = 50__ B: REWARD_RATE = 50 block.timestamp = 2023 lastTime = 2021 amount = 100 __reward = 100__ Now B, who has a longer deposit period, will receive a larger reward. Isn't this what this contract wanted to do? ## Summary:[M-06] Mistake of type ### Vulnerability Detail The `isETH` in `struct TransferInfo` is `uint`. ### Impact Should be a true/false value whether or not it is ETH ### Code Snippet ```solidity=27 struct TransferInfo { uint isETH; /// 32 bytes uint amount; /// 32 bytes address token; /// 20 bytes address from; /// 20 bytes address to; /// 20 bytes } ``` ### Tool used Manual Review ### Recommendation ```solidity=27 struct TransferInfo { bool isETH; /// 32 bytes uint amount; /// 32 bytes address token; /// 20 bytes address from; /// 20 bytes address to; /// 20 bytes } ``` ## Summary:[M-07] Requires the payable modifier ### Vulnerability Detail If `isETH` is `true`, `payable` modifier is needed because `value` is transferred by `call`. ### Impact Cannot remit in case of ETH. ### Code Snippet ```solidity=124 function _tokenTransfer(TransferInfo memory _info) private { if (_info.isETH) { (bool success, ) = _info.to.call{ value: _info.amount }(""); require(success, "Failed"); } else { IERC20(_info.token).transferFrom(_info.from, _info.to, _info.amount); } } ``` ### Tool used Manual Review ### Recommendation ```solidity=124 function _tokenTransfer(TransferInfo memory _info) private payable { if (_info.isETH) { (bool success, ) = _info.to.call{ value: _info.amount }(""); require(success, "Failed"); } else { IERC20(_info.token).transferFrom(_info.from, _info.to, _info.amount); } } ```