Sublime contest - kyliek'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: 3/24

Findings: 2

Award: $2,876.49

🌟 Selected for report: 1

🚀 Solo Findings: 1

Findings Information

🌟 Selected for report: kyliek

Labels

bug
question
2 (Med Risk)

Awards

2590.5858 USDC - $2,590.59

External Links

Lines of code

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

Vulnerability details

Impact

An attacker could keep track of the totalSupply of each LenderPool to see if it is more than the minBorrowAmount. If so, at startTime, which is pre-announced, the attacker could call start, which will trigger SAVINGS_ACCOUNT.deposit() of the entire pool assets to mint LP tokens from external strategy, for example, in CompoundYield.

There is potentially a big sum depositing into a known Compound cToken contract at a known fixed time. Thus, the attacker could prepare the pool by depositing a fair sum first to lower the exchange rate before calling start in lenderPool. Hence, the deposit of the entire pool could be at a less favourable rate. This also applies to other potential strategies that are yet to be integrated. For example, in Curve pool, the attacker could prime the pool to be very imbalanced first and trigger the deposit and then harvest the arbitrage bonus by bringing the pool back to balance.

This attack can happen once only when the pooledCreditLine becomes active for each new lenderPool.

Proof of Concept

Step 1: When a new LenderPool started, note the borrowAsset token and its strategy target pool, as well as the collection period (i.e. start time)

Step 2: Closer to the start time block number, if totalSupply of the lenderPool is bigger than the minBorrowAmount, prepare a good sum to manipulate the target strategy pool for unfavorable exchange rate or arbitrage opportunity afterwords.

Step 3: Call start function before others, also put in his own address to _to to pocket the protocol fee.

Step 4: Depending on the strategy pool, harvest arbitrage. Or perhaps just withdraw one's money from Step 2 for griefing.

Tools Used

Add access control on start, e.g. only borrower can call through pooledCreditLine.

#0 - ritik99

2022-04-12T13:06:45Z

We're not sure how this leads to an attack. Fluctuation in yield is known and expected for most yield strategies like Compound. This, however, does not cause a loss of funds. An attacker instantly withdrawing their liquidity doesn't affect others because interest rates are not "locked in" on the yield strategies. There are no exchanges taking place. Some more info on possible attacks might help

#1 - HardlyDifficult

2022-05-02T14:48:18Z

Followed up with the warden and there appears to be a way to leak value by debalancing the pool before start and then rebalancing to extract some profit. This could be done with a flashbot for example to limit exposure.

The warden referenced https://github.com/yearn/yearn-security/blob/master/disclosures/2021-02-04.md as an example of something similar.

#2 - ritik99

2022-05-18T06:56:56Z

Had a discussion with the warden and the judge regarding this issue. For Compound in particular we checked that the exchange rate does not change upon deposits or withdrawals. Thus, sandwiching a call to start() couldn't possibly lead to an attack vector. Additionally, because of another issue related to start fees #19, we decided to restrict start() to be callable only by the borrower.

However, the yield strategies that we whitelist might still be internally susceptible to this attack (for eg, https://github.com/yearn/yearn-security/blob/master/disclosures/2021-02-04.md). We'll be incorporating checks for this while onboarding strategies. Picking riskier strategies is optional and not enforced at the contract-level

Findings Information

Awards

285.8952 USDC - $285.90

Labels

bug
QA (Quality Assurance)

External Links

General comments

The codebase is generally very well commented and functions grouped together in a very sensible and easy to read fashion. Despite the complexity of the project, the scope for this audit is limited to Pooled Credit Lines. I am generally impressed by the code quality and the genuine effort the developers have put in to make it as easy to comprehend as possible.

LOW RISK

[L01] Anyone can call withdrawInterest in LenderPool to reduce compounding effect for lenders in strategy

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

withdraw function has no access control. Since lender address is on chain, anyone could call this function to withdraw a lender's interest to a lender's account.

This will not benefit the caller but will reduce the compounding effect for lenders in their strategy. It is better to use msg.sender so that only caller who is a lender can withdraw its own interest.

NON-CRITICAL informational

[N01] Risk accumulation by opening up multiple pooledCreditLine back to back

There is no restriction on the number of concurrent pooledCreditLine a borrower can request. Thus, this could create on its own a dynamic where the borrower opens a line to feed a previous line to get good credits with lenders for under-collateralised loans on the same platform and at the same time accumulate risks for default at some point in the future. That would be the point that the lenders suffer the most.

[N02] collateralAsset is not initialised in create

When a lenderPool is created, the pooledCLConstants are initialised. However, collateralAsset is not initialised at the start but only updated later in liquidate. I wonder what is the advantage of this since the borrower will supply this value at the time of request creation.

Affected code lines: https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/PooledCreditLine.sol#L681

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

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

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

[N03] borrowLimit changed at accept stage, thus not a constant

The borrower proposed a borrowLimit recorded in the pooledCreditLineConstants[id] in the creation of the request.

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

Then it is recorded as a constant too in the corresponding lenderPool in create().

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

Later on, depending on the amount collected from the lenders, this value is updated to be all available borrowAssets contributed to the lenderPool minus fees.

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

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

Thus, it is rather confusing that this is included as a pool constant instead of a pool variable as it has changed value in the process.

[N04] Cryptic revert message throughout

The revert messages are in code and it is very cryptic to users when a transaction didn't go through. For example, https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/PooledCreditLine.sol#L209 https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/PooledCreditLine.sol#L217

I see error codes are explained here https://docs.sublime.finance/sublime-docs/smart-contracts/pooled-credit-lines#error-codes in the doc. However, it would be nice to have a simple explainable on-chain message from UX viewpoint.

[N05] Struct and mapping names too similar

The mapping and the corresponding Struct names are too similar, except for the first letter in capital or not. These get a bit confusing and could lead to errors too.

Instances are https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/PooledCreditLine.sol#L193 https://github.com/sublime-finance/sublime-v1/blob/46536a6d25df4264c1b217bd3232af30355dcb95/contracts/PooledCreditLine/PooledCreditLine.sol#L198

[N06] Duplicated code
_clc.collateralAssetStrategy = _request.collateralAssetStrategy;

This line is duplicated twice in the same block.

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

[N07] Naming not consistent

borrowToken is used sometimes to refer to borrowAsset. Replace borrowTokenwith borrowAsset to be consistent throughout.

Instances are

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

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

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

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

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

[N08] Empty returns are not informative

There are a few instances where a function gives an empty return without any extra information. It would be useful to give more context on which control if condition is reached.

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

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

#0 - ritik99

2022-04-12T19:43:16Z

Happy to hear that the comments helped and that the contracts are readable. Thank you for the comment!

The suggestions made by the warden are all relevant. [L01] is a known trade-off, we'd implemented it this way so that it is possible to abstract the chore of calling withdrawals periodically by a third-party service provider/keeper. However, we might consider restricting the caller to lenders (who can only withdraw their own interests)

The non-critical suggestions are all valid, we would try to incorporate them into our code. The report is of high quality

#1 - HardlyDifficult

2022-04-26T15:19:28Z

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