Sublime contest - rayn's results

Democratizing credit via Web3.

General Information

Platform: Code4rena

Start Date: 29/03/2022

Pot Size: $30,000 USDC

Total HM: 6

Participants: 24

Period: 3 days

Judge: HardlyDifficult

Total Solo HM: 4

Id: 101

League: ETH

Sublime

Findings Distribution

Researcher Performance

Rank: 4/24

Findings: 3

Award: $2,463.71

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: hickuphh3

Also found by: WatchPug, rayn

Labels

bug
duplicate
3 (High Risk)

Awards

2331.5273 USDC - $2,331.53

External Links

Lines of code

https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/LenderPool.sol#L406

Vulnerability details

Impact

While calculating the amount of shares to withdraw in LenderPool.terminate, _principalWithdrawable (which is amount of tokens) is added to _totalInterestInShares, and then passed to SAVINGS_ACCOUNT.withdrawShares. If tokens : shares are not 1 : 1, which is the norm for yields, incorrect amounts will be withdrawn. Since one share is usually worth more than one token, the incorrect amount will usually be higher than the intended amount, and result in stolen funds from other pools.

Proof of Concept

It is easy to see that _principalWithdrawable is amount of tokens and _totalInterestInShares is amount of shares in the code below.

address _borrowAsset = pooledCLConstants[_id].borrowAsset; uint256 _borrowedTokens = pooledCLConstants[_id].borrowLimit; uint256 _notBorrowed = _borrowedTokens.sub(POOLED_CREDIT_LINE.getPrincipal(_id)); uint256 _notBorrowedInShares = IYield(_strategy).getSharesForTokens(_notBorrowed, _borrowAsset); uint256 _sharesHeld = pooledCLVariables[_id].sharesHeld; uint256 _totalInterestInShares = _sharesHeld.sub(_notBorrowedInShares); uint256 _principalWithdrawable = _notBorrowed.mul(totalSupply[_id]).div(_borrowedTokens); SAVINGS_ACCOUNT.withdrawShares(_borrowAsset, _strategy, _to, _principalWithdrawable.add(_totalInterestInShares), false);

Under any scenario, it is unreasonable to mix calculation of both, and for the specific case here, _principalWithdrawable must be converted to the amount of shares for the withdrawal to work correctly.

Tools Used

vim, ganache-cli

Convert _principalWithdrawable to shares before withdrawing. Or honestly, just withdraw all _sharesHeld in the pool and avoid those complex calculations altogether.

#0 - ritik99

2022-04-12T12:06:47Z

Duplicate of #21

Findings Information

Awards

94.016 USDC - $94.02

Labels

bug
QA (Quality Assurance)

External Links

Summary

We list 6 low-critical findings and 6 non-critical findings here:

  • (Low) LenderPool.withdrawInterest allows withdrawing interest for arbitrary lender
  • (Low) LenderPool.calculatePrincipalWithdrawable does not take into consideration status == CANCELLED
  • (Low) PooledCreditLine.request priceOracle fallback to uniswap, and anyone can register uniswap for arbitrary tokens, it is possible to use malicious tokens
  • (Low) Use safeTransfer to check return value
  • (Low) Uninitialized variable in repay function of PooledCreditLine.sol
  • (Low) Everyone can call LenderPool.start
  • (Non) PooledCreditLine.repay can be called by anyone
  • (Non) Approve amount always type(uint256).max
  • (Non) Lender.Pool._beforeTokenTransfer consider using continue instead
  • (Non) Uses non upgradeable version of reentrancy guard contract
  • (Non) Variable assigns twice
  • (Non) LenderPool and PooledCreditLine is not upgrade safe

In summary of recommended security practices, it's better to restrict function access, use 3rd party libraries for security (e.g. safeTransfer, ReentrancyGuardUpgradeable), and take consideration of status.

(Low) LenderPool.withdrawInterest allows withdrawing interest for arbitrary lender

Impact

LenderPool.withdrawInterest allows lenders to retrieve the interest that is owed to them. However it doesn’t check the weather msg.sender is lender. Which means an attacker can call LenderPool.withdrawInterest in order to make others retrieve their interest. It could be harmless, the attacker can get any interest. But the lenders may not want to retrieve their interest at that moment.

Proof of Concept

https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/LenderPool.sol#L425

Use msg.sender as lender.

(Low) LenderPool.calculatePrincipalWithdrawable does not take into consideration status == CANCELLED

Impact

If _status == PooledCreditLineStatus.CANCELLED, LenderPool.calculatePrincipalWithdrawable returns 0. But it is not the correct number.

Proof of Concept

https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/LenderPool.sol#L579

Take PooledCreditLineStatus.CANCELLED into consideration.

Add _status == PooledCreditLineStatus.CANCELLED into the statement of if condition.

(Low) PooledCreditLine.request priceOracle fallback to uniswap, and anyone can register uniswap for arbitrary tokens, it is possible to use malicious tokens

Impact

Check on borrow/collateral asset relies on IPriceOracle(priceOracle).doesFeedExist(_request.borrowAsset, _request.collateralAsset). However, since priceOracle falls back to uniswap, and anyone can register uniswap for arbitrary tokens, it is possible to use malicious tokens. While lenders should probably be sensible enough to avoid those kinds of pools, it is hard to judge the difficulty of telling between benign uniswap pair and potentially malicious uniswap pair (most users probably just check name).

Proof of Concept

https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/PooledCreditLine.sol#L641

Use whitelist to prevent malicious uniswap pair.

(Low) Use safeTransfer to check return value

https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/LenderPool.sol#L327

Impact

If ERC20 transfer fails, it will return false rather than revert, and lose _fee of _to address.

Proof of Concept

https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/LenderPool.sol#L327

Use safeTransfer to check the return value:

IERC20(_borrowAsset).safeTransfer(_to, _fee);

(Low) Uninitialized variable in repay function of PooledCreditLine.sol

Impact

The variable uint256 _principalPaid is uninitialized. In the case here, solc-bin implicitly sets uninitialized stack value to 0, but this feature is undocumented and not guaranteed in solc, thus it would be best to do an explicit initialization here.

Proof of Concept

https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/PooledCreditLine.sol#L994

If _amount <= _interestToPay, _principalPaid will be uninitialized.

Initialize _principalPaid to 0:

uint256 _principalPaid = 0;

(Low) Everyone can call LenderPool.start

Impact

start function in LenderPool does not check _to, so anyone can call this function and claim the _fee.

Proof of Concept

https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/LenderPool.sol#L312

It’s better to limit the caller who is involved in.

(Non) PooledCreditLine.repay can be called by anyone

Impact

PooledCreditLine.repay can be called by any user. It doesn’t check whether msg.sender is who should repay. However it won’t cause any problem. Also it might be intended.

Proof of Concept

https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/PooledCreditLine.sol#L976

Check the relationship between msg.sender and PooledCreditLine.

(Non) Approve amount always type(uint256).max

Impact

In create function of LenderPool, it always approves type(uint256).max amount.

Proof of Concept

https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/LenderPool.sol#L267

It’s better to use a limited amount rather than type(uint256).max.

(Non) Lender.Pool._beforeTokenTransfer consider using continue instead

Impact

_beforeTokenTransfer has an ids array, it’s better to use continue in the for loop rather than return.

Proof of Concept

https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/LenderPool.sol#L687

if (from == address(0)) { continue; }

(Non) Uses non upgradeable version of reentrancy guard contract

Impact

Use a non upgradeable version of reentrancy guard will not initialize when using an upgradable contract. Also it will not reserve empty space to allow future versions.

Proof of Concept

https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/PooledCreditLine.sol#L25 https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/LenderPool.sol#L25

Use ReentrancyGuardUpgradeable: https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/security/ReentrancyGuardUpgradeable.sol

(Non) Variable assigns twice

Impact

The variable _clc.collateralAssetStrategy is assigned twice in _createRequest function of PooledCreditLine.

Proof of Concept

https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/PooledCreditLine.sol#L682 https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/PooledCreditLine.sol#L689

Just assign once.

(Non) LenderPool and PooledCreditLine is not upgrade safe

Impact

Using constructor in upgradable contract is not upgrade safe. It will cause an error when deploying an upgradable contract by hardhat: Error: Contract XXXXX is not upgrade safe.

Proof of Concept

https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/PooledCreditLine.sol#L592-L595 https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/LenderPool.sol#L205-L213

It’s better to put immutable variables into initialize function.

#0 - ritik99

2022-04-13T10:33:36Z

  1. "LenderPool.withdrawInterest allows withdrawing interest for arbitrary lenders": This was a design choice made to enable abstractions to be build that periodically withdraws interests by a keeper
  2. "PooledCreditLine.request priceOracle fallback to uniswap, and anyone can register uniswap for arbitrary tokens, it is possible to use malicious tokens": All tokens must be added before they can be used
  3. "Everyone can call LenderPool.start": An external call is necessary to make the necessary state changes. This has been done intentionally to allow anyone to call the start() function, because it also triggers the deposit of assets into the savings strategy
  4. "PooledCreditLine.repay can be called by anyone": This has been done intentionally to allow repayments to happen from an address that didn't create the loan. This provides more flexibility for the borrower to repay loans/build abstractions

Findings Information

Awards

38.1576 USDC - $38.16

Labels

bug
G (Gas Optimization)

External Links

Use private variables to save gas

They can be declared private for gas savings, only creating view functions for external read.

Proof of Concept

https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/PooledCreditLine.sol#L34-L75

Recommendation

Declare these variables to private.

#0 - ritik99

2022-04-12T19:03:32Z

Suggestion is valid

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