OpenLeverage contest - hyh's results

Permissionless lending and margin trading protocol that enables traders to long/short any trading pair on DEXs efficiently and securely.

General Information

Platform: Code4rena

Start Date: 27/01/2022

Pot Size: $75,000 USDT

Total HM: 6

Participants: 29

Period: 7 days

Judge: leastwood

Total Solo HM: 6

Id: 72

League: ETH

OpenLeverage

Findings Distribution

Researcher Performance

Rank: 1/29

Findings: 4

Award: $24,920.00

🌟 Selected for report: 6

🚀 Solo Findings: 2

Findings Information

🌟 Selected for report: hyh

Labels

bug
3 (High Risk)
resolved
sponsor confirmed

Awards

16276.0417 USDT - $16,276.04

External Links

Handle

hyh

Vulnerability details

Impact

When OpenLev operations use a wrapped native token, the whole user withdraw is being handled with a payable.transfer() call.

This is unsafe as transfer has hard coded gas budget and can fail when the user is a smart contract. This way any programmatical usage of OpenLevV1 and LPool is at risk.

Whenever the user either fails to implement the payable fallback function or cumulative gas cost of the function sequence invoked on a native token transfer exceeds 2300 gas consumption limit the native tokens sent end up undelivered and the corresponding user funds return functionality will fail each time.

As OpenLevV1 closeTrade is affected this includes user's principal funds freeze scenario, so marking the issue as a high severity one.

Proof of Concept

OpenLevV1Lib and LPool have doTransferOut function that calls native token payable.transfer:

OpenLevV1Lib.doTransferOut

https://github.com/code-423n4/2022-01-openleverage/blob/main/openleverage-contracts/contracts/OpenLevV1Lib.sol#L253

LPool.doTransferOut

https://github.com/code-423n4/2022-01-openleverage/blob/main/openleverage-contracts/contracts/liquidity/LPool.sol#L297

LPool.doTransferOut is used in LPool redeem and borrow, while OpenLevV1Lib.doTransferOut is used in OpenLevV1 trade manipulation logic:

closeTrade

https://github.com/code-423n4/2022-01-openleverage/blob/main/openleverage-contracts/contracts/OpenLevV1.sol#L204

https://github.com/code-423n4/2022-01-openleverage/blob/main/openleverage-contracts/contracts/OpenLevV1.sol#L215

liquidate

https://github.com/code-423n4/2022-01-openleverage/blob/main/openleverage-contracts/contracts/OpenLevV1.sol#L263

https://github.com/code-423n4/2022-01-openleverage/blob/main/openleverage-contracts/contracts/OpenLevV1.sol#L295

https://github.com/code-423n4/2022-01-openleverage/blob/main/openleverage-contracts/contracts/OpenLevV1.sol#L304

References

The issues with transfer() are outlined here:

https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/

OpenLevV1's closeTrade and liquidate as well as LPool's redeem, redeemUnderlying, borrowBehalf, repayBorrowBehalf, repayBorrowEndByOpenLev are all nonReentrant, so reentrancy isn't an issue and transfer() can be just replaced.

Using low-level call.value(amount) with the corresponding result check or using the OpenZeppelin Address.sendValue is advised:

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol#L60

#0 - 0xleastwood

2022-02-19T03:03:29Z

Awesome find! Completely agree with the warden here. This would prevent users from calling sensitive functions which withdraw their funds in some way.

Findings Information

🌟 Selected for report: hyh

Labels

bug
2 (Med Risk)
resolved
sponsor confirmed

Awards

4882.8125 USDT - $4,882.81

External Links

Handle

hyh

Vulnerability details

Impact

The amount that OpenLevV1 will receive can be less than V3 DEX indicated as a swap result, while it is used as given for position debt repayment accounting.

This way actual funds received can be less than accounted, leaving to system funds deficit, which can be exploited by a malicious user, draining contract funds with multiple open/close with a taxed token.

In the trade.depositToken != longToken case when flashSell is used this can imply inability to send remainder funds to a user and the failure of the whole closeTrade function, the end result is a freezing of user's funds within the system.

Proof of Concept

trade.depositToken != longToken case, can be wrong repayment accounting, which will lead to a deficit if the received funds are less than DEX returned closeTradeVars.receiveAmount.

As a side effect, doTransferOut is done without balance check, so the whole position close can revert, leading to inability to close the position and freeze of user's funds this way:

https://github.com/code-423n4/2022-01-openleverage/blob/main/openleverage-contracts/contracts/OpenLevV1.sol#L197-204

I.e. if there is enough funds in the system they will be drained, if there is not enough funds, user's position close will fail.

V3 sell function doesn't check for balance change, using DEX returned amount as is:

https://github.com/code-423n4/2022-01-openleverage/blob/main/openleverage-contracts/contracts/dex/eth/UniV3Dex.sol#L61-70

If fee on tranfer tokens are fully in scope, do control all the accounting and amounts to be returned to a user via balance before/after calculations for DEX V3 logic as well.

#0 - 0xleastwood

2022-02-19T11:31:28Z

Awesome find. I was able to confirm that UniV3Dex.uniV3Sell() does not properly handle fee-on-transfer tokens by treating the amount received as the difference between before balance and after balance.

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