Yeti Finance contest - csanuragjain's results

Portfolio borrowing. 11x leverage. 0% interest.

General Information

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

Yeti Finance

Findings Distribution

Researcher Performance

Rank: 9/25

Findings: 5

Award: $3,290.18

🌟 Selected for report: 6

🚀 Solo Findings: 1

Findings Information

🌟 Selected for report: csanuragjain

Labels

bug
2 (Med Risk)
sponsor disputed

Awards

1593.8842 USDC - $1,593.88

External Links

Handle

csanuragjain

Vulnerability details

Impact

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)

Proof of Concept

  1. Navigate to contract at https://github.com/code-423n4/2021-12-yetifinance/blob/main/packages/contracts/contracts/ActivePool.sol

  2. 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; }
  1. This calls _sendCollateral with our non whitelisted token and amount as 1
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); }
  1. 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

  2. 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);
  1. 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

  2. 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.

Findings Information

🌟 Selected for report: cmichel

Also found by: csanuragjain, gzeon

Labels

bug
duplicate
2 (Med Risk)
disagree with severity
sponsor confirmed

Awards

430.3487 USDC - $430.35

External Links

Handle

csanuragjain

Vulnerability details

Impact

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

Proof of Concept

  1. Navigate to contract at https://github.com/code-423n4/2021-12-yetifinance/blob/main/packages/contracts/contracts/Dependencies/Whitelist.sol

  2. Assume addCollateral function is called by owner with collateral address say 0xabc...

  3. This collateral gets pushed to the system via validCollateral.push(_collateral);

  4. 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"); }
  1. 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)

  2. 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.

#2 - alcueca

2022-01-14T21:43:25Z

Taking #198 as the main

Findings Information

🌟 Selected for report: csanuragjain

Also found by: hyh, jayjonah8

Labels

bug
2 (Med Risk)
disagree with severity

Awards

430.3487 USDC - $430.35

External Links

Handle

csanuragjain

Vulnerability details

Impact

Monetary loss for user

Proof of Concept

  1. Navigate to contract at https://github.com/code-423n4/2021-12-yetifinance/blob/main/packages/contracts/contracts/AssetWrappers/WJLP/WJLP.sol

  2. 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); }
  1. Lets say user reward are calculated to be 100 so _safeJoeTransfer is called with joeToSend as 100. Also user remaining reward becomes 0

  2. 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); } }
  1. If the reward balance left in this contract is 90 then _safeJoeTransfer will pass if condition and contract will transfer 90 amount. Thus user incur a loss of 100-90=10 amount (his remaining reward are already set to 0)

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.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter