Platform: Code4rena
Start Date: 16/12/2021
Pot Size: $100,000 USDC
Total HM: 21
Participants: 25
Period: 7 days
Judge: alcueca
Total Solo HM: 12
Id: 66
League: ETH
Rank: 9/25
Findings: 5
Award: $3,290.18
🌟 Selected for report: 6
🚀 Solo Findings: 1
🌟 Selected for report: csanuragjain
1593.8842 USDC - $1,593.88
csanuragjain
Contract instability and financial loss. This will happen if one of the allowed contract calls sendCollaterals with non whitelisted token (may happen with user input on allowed contract)
Navigate to contract at https://github.com/code-423n4/2021-12-yetifinance/blob/main/packages/contracts/contracts/ActivePool.sol
Assume sendCollaterals function is called by one of allowed contract with a non whitelisted token and amount as 1
function sendCollaterals(address _to, address[] memory _tokens, uint[] memory _amounts) external override returns (bool) { _requireCallerIsBOorTroveMorTMLorSP(); require(_tokens.length == _amounts.length); for (uint i = 0; i < _tokens.length; i++) { _sendCollateral(_to, _tokens[i], _amounts[i]); // reverts if send fails } if (_needsUpdateCollateral(_to)) { ICollateralReceiver(_to).receiveCollateral(_tokens, _amounts); } return true; }
function _sendCollateral(address _to, address _collateral, uint _amount) internal returns (bool) { uint index = whitelist.getIndex(_collateral); poolColl.amounts[index] = poolColl.amounts[index].sub(_amount); bool sent = IERC20(_collateral).transfer(_to, _amount); require(sent); emit ActivePoolBalanceUpdated(_collateral, _amount); emit CollateralSent(_collateral, _to, _amount); }
whitelist.getIndex(_collateral); will return 0 as our collateral is not whitelisted and will not be present in whitelist.getIndex(_collateral);. This means index will point to whitelisted collateral at index 0
poolColl.amounts[index] will get updated for whitelisted collateral at index 0 even though this collateral was never meant to be updated
poolColl.amounts[index] = poolColl.amounts[index].sub(_amount);
Finally our non supported token gets transferred to recipient and since _needsUpdateCollateral is true so recipient poolColl.amounts gets increased even though recipient never received the whitelisted collateral
Finally sender pool amount will be reduced even though it has the whitelisted collateral and recipient pool amount will be increased even though it does not have whitelisted collateral
Add a check to see if collateral to be transferred is whitelisted
#0 - kingyetifinance
2022-01-05T07:13:06Z
@LilYeti: Thanks for the thorough run through. It is true, but this is abstracted away, all calls of sendCollateral are internal / between contracts in our codebase, and there are checks for valid collateral in whitelist before this.
#1 - alcueca
2022-01-16T07:05:02Z
Validating data integrity outside a function inside the same contract would be a low severity. Validating data integrity in an external contract is medium severity. Many things can go wrong.
#2 - 0xtruco
2022-02-04T00:43:14Z
@alcueca I understand that this requires some looking into to confirm this but if we had to check every input for external functions which are only callable by other contracts in our codebase, then that would be extremely gas inefficient.
#3 - alcueca
2022-02-04T06:13:38Z
Sorry, I don't make the rules. Maybe you need to think your architecture thoroughly to avoid this issue.
🌟 Selected for report: cmichel
Also found by: csanuragjain, gzeon
430.3487 USDC - $430.35
csanuragjain
Duplicate collaterals can be added which makes getValidCollateral return duplicate items. This impacts all function which uses getValidCollateral function like _getPendingCollRewards, which will now calculate the pending reward twice for the duplicate item. This only impact collateral at index 0
Navigate to contract at https://github.com/code-423n4/2021-12-yetifinance/blob/main/packages/contracts/contracts/Dependencies/Whitelist.sol
Assume addCollateral function is called by owner with collateral address say 0xabc...
This collateral gets pushed to the system via validCollateral.push(_collateral);
Admin calls addCollateral function again and by mistake passes collateral address as 0xabc... again. Below if condition is bypassed since validCollateral[0] != _collateral is false (This collateral is already at index 0)
if (validCollateral.length != 0 && validCollateral[0] != _collateral) { require(collateralParams[_collateral].index == 0, "collateral already exists"); }
This means now collateral 0xabc... is present at both index 0 and 1 of validCollateral. Also the previous collateralParams[0xabc...] for index 0 will be overwritten (say if admin disabled this collateral initially, this will get reenabled once duplicate entry is added)
This will also impact all function calling getValidCollateral as now they will get duplicate collateral and thus will perform operation twice on same token (_getPendingCollRewards, which will now calculate the pending reward twice for the duplicate item.)
Return error if owner adds a collateral which is already present at index 0
#0 - kingyetifinance
2022-01-05T07:01:26Z
@LilYeti : The check only is invalid to add duplicate coll in the whitelist for the first collateral added. Downgrading to medium since it only applies to one collateral which will probably be wAVAX.
#1 - 0xtruco
2022-01-11T10:41:03Z
#2 - alcueca
2022-01-14T21:43:25Z
Taking #198 as the main
🌟 Selected for report: csanuragjain
430.3487 USDC - $430.35
csanuragjain
Monetary loss for user
Navigate to contract at https://github.com/code-423n4/2021-12-yetifinance/blob/main/packages/contracts/contracts/AssetWrappers/WJLP/WJLP.sol
Let us see _sendJoeReward function
function _sendJoeReward(address _rewardOwner, address _to) internal { // harvests all JOE that the WJLP contract is owed _MasterChefJoe.withdraw(_poolPid, 0); // updates user.unclaimedJOEReward with latest data from TJ _userUpdate(_rewardOwner, 0, true); uint joeToSend = userInfo[_rewardOwner].unclaimedJOEReward; userInfo[_rewardOwner].unclaimedJOEReward = 0; _safeJoeTransfer(_to, joeToSend); }
Lets say user reward are calculated to be 100 so _safeJoeTransfer is called with joeToSend as 100. Also user remaining reward becomes 0
Let us see _safeJoeTransfer function
function _safeJoeTransfer(address _to, uint256 _amount) internal { uint256 joeBal = JOE.balanceOf(address(this)); if (_amount > joeBal) { JOE.transfer(_to, joeBal); } else { JOE.transfer(_to, _amount); } }
If the reward balance is lower than user balance then contract must transfer reward balance in contract and make remaining user reward balance as ( user reward balance - contract reward balance )
#0 - kingyetifinance
2022-01-07T07:28:01Z
Duplicate #61
#1 - kingyetifinance
2022-01-07T07:30:58Z
@LilYeti: In #61 it has description and explanation why should be severity 1.
#2 - alcueca
2022-01-15T06:37:32Z
Taking as main
#3 - alcueca
2022-01-15T06:38:45Z
From #61:
@LilYeti : MasterChef is a decently well trusted contract and all JLP rewards are distributed there. Fundamentally the number should not be off by any, if any will be dust, and this exists to protect in the worst case so at least some users can get JOE out. However it is a backstop and extra safety measure. In #137 the reward being off by 10 would require an additional bug somewhere else, or a failure of MasterChef.
🌟 Selected for report: defsec
Also found by: 0x1f8b, Jujic, WatchPug, broccolirob, certora, cmichel, csanuragjain, hyh, jayjonah8, kenzo, robee, sirhashalot
csanuragjain
Using transfer may not guarantee actual transfer
Observe that sendToken function in https://github.com/code-423n4/2021-12-yetifinance/blob/main/packages/contracts/contracts/YetiFinanceTreasury.sol is using transfer instead of safetransfer
Use safetransfer instead of transfer
#0 - kingyetifinance
2022-01-05T05:59:23Z
@LilYeti: Duplicate with issue #1
#1 - kingyetifinance
2022-01-10T06:08:22Z
Fixed
#2 - alcueca
2022-01-15T07:32:44Z
Duplicate of #94
🌟 Selected for report: csanuragjain
531.2947 USDC - $531.29
csanuragjain
receiveCollateral is not called on sendCollateralsUnwrap due to which target pool does not get updated with the transferred token amount
Navigate to contract at https://github.com/code-423n4/2021-12-yetifinance/blob/main/packages/contracts/contracts/ActivePool.sol
Observe that sendCollateralsUnwrap function is not calling receiveCollateral and thus target collateral are never updated (reference is sendCollaterals function)
Add below lines in sendCollateralsUnwrap
if (_needsUpdateCollateral(_to)) { ICollateralReceiver(_to).receiveCollateral(_tokens, _amounts); }
#0 - kingyetifinance
2022-01-05T07:09:02Z
@LilYeti: True, but this function is never called to transfer and unwrap between pools. That is because only the wrapped version of the collateral is whitelisted.
#1 - alcueca
2022-01-16T07:18:50Z
Downgraded to low severity since there isn't an actual error in the code, but there could be through future implementations that are not aware of this particularity.
🌟 Selected for report: csanuragjain
97.5905 USDC - $97.59
csanuragjain
Gas saving
Navigate to contract at https://github.com/code-423n4/2021-12-yetifinance/blob/main/packages/contracts/contracts/Dependencies/CheckContract.sol
Observe the checkContract function
function checkContract(address _account) internal view { require(_account != address(0), "Account cannot be zero address"); uint256 size; // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(_account) } require(size > 0, "Account code size cannot be zero"); }
#0 - kingyetifinance
2022-01-06T08:44:55Z
@LilYeti: Need to confirm / is more clear this way. Don't really know what will be returned if that check is removed and addr 0 is used. Will change if confirmed.
🌟 Selected for report: csanuragjain
97.5905 USDC - $97.59
csanuragjain
Gas saving
Navigate to contract at https://github.com/code-423n4/2021-12-yetifinance/blob/main/packages/contracts/contracts/Dependencies/TellorCaller.sol
Observe that tellor variable can be made immutable
Similarly yetiToken in LockupContract.sol, yetiTokenAddress in LockupContractFactory
#0 - kingyetifinance
2022-01-06T08:46:03Z
Similar to #132 and #10 but has some different variables.
#1 - 0xtruco
2022-01-14T07:40:49Z
Not using tellor
🌟 Selected for report: csanuragjain
97.5905 USDC - $97.59
csanuragjain
Gas wastage
Navigate to contract at https://github.com/code-423n4/2021-12-yetifinance/blob/main/packages/contracts/contracts/Dependencies/YetiCustomBase.sol
Observe that in _subColls function the last for loop is not required if n=0 since this means that all token amount is 0