Lybra Finance - devival's results

A protocol building the first interest-bearing omnichain stablecoin backed by LSD.

General Information

Platform: Code4rena

Start Date: 23/06/2023

Pot Size: $60,500 USDC

Total HM: 31

Participants: 132

Period: 10 days

Judge: 0xean

Total Solo HM: 10

Id: 254

League: ETH

Lybra Finance

Findings Distribution

Researcher Performance

Rank: 7/132

Findings: 6

Award: $1,674.57

QA:
grade-b

🌟 Selected for report: 1

πŸš€ Solo Findings: 1

Findings Information

🌟 Selected for report: T1MOH

Also found by: 0xnev, Iurii3, KupiaSec, LaScaloneta, bytes032, cccz, devival, josephdara, pep7siup, sces60107, skyge, yjrwkk

Labels

bug
3 (High Risk)
satisfactory
edited-by-warden
duplicate-15

Awards

80.4648 USDC - $80.46

External Links

Lines of code

https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/governance/LybraGovernance.sol#L120-L122

Vulnerability details

Impact

Detailed description of the impact of this finding.

Even though, the votes are calculated correctly, the proposals view function returns wrong voting results returning forVotes results as againstVotes amount.

This would negatively impact the users understanding of the proposals voting results and lead to confusions.

Proof of Concept

Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

The Governance COUNTING_MODE() is set Bravo as follows:

function COUNTING_MODE() public override view virtual returns (string memory){ return "support=bravo&quorum=for,abstain"; }

LybraGovernance.sol#L156

This refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in GovernorBravo. Openzepping#Governor,

The total amount of votes are stored in proposalData[proposalId].supportVotes[X], where X, as abovementioned, can be 0 = Against, 1 = For, 2 = Abstain:

struct ProposalExtraData { mapping(address => Receipt) receipts; mapping(uint8 => uint256) supportVotes; uint256 totalVotes; } mapping (uint256 => ProposalExtraData) public proposalData;

LybraGovernance.sol#L27

Finally, this is the proposals function which returns forVotes as a total amount of votes against, which corresponds to proposalData[proposalId].supportVotes[0]:

function proposals(uint256 proposalId) external view returns (uint256 id, address proposer, uint256 eta, uint256 startBlock, uint256 endBlock, uint256 forVotes, uint256 againstVotes, uint256 abstainVotes, bool canceled, bool executed) { id = proposalId; //... forVotes = proposalData[proposalId].supportVotes[0]; againstVotes = proposalData[proposalId].supportVotes[1]; abstainVotes = proposalData[proposalId].supportVotes[2]; //... }

LybraGovernance.sol#L120-L122

Tools Used

Manual Review

In LybraGovernance.sol#L120-L122 replace

forVotes = proposalData[proposalId].supportVotes[0]; againstVotes = proposalData[proposalId].supportVotes[1];

with

againstVotes = proposalData[proposalId].supportVotes[0]; forVotes = proposalData[proposalId].supportVotes[1];

Assessed type

Other

#0 - c4-pre-sort

2023-07-04T13:39:25Z

JeffCX marked the issue as duplicate of #15

#1 - c4-judge

2023-07-28T15:33:01Z

0xean marked the issue as satisfactory

Findings Information

🌟 Selected for report: devival

Labels

bug
2 (Med Risk)
primary issue
satisfactory
selected for report
sponsor confirmed
M-01

Awards

1444.4545 USDC - $1,444.45

External Links

Lines of code

https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/governance/LybraGovernance.sol#L172-L174

Vulnerability details

Impact

The proposal can be created with only 100_000 esLBR delegated instead of 10_000_000.

Proof of Concept

According to LybraV2Docs, a proposal can only be created if the sender has at least 10 million esLBR tokens delegated to his address to meet the proposal threshold.

In LybraGovernance.sol#L172-L174 the proposal threshold is set to only 1e23 which equals to 100_000 as esLBR has 18 decimals.

function proposalThreshold() public pure override returns (uint256) { return 1e23; }

LybraGovernance.sol#L172-L174

Tools Used

Manual Review

In LybraGovernance.sol#L173 replace 1e23 with 1e25

Alternatively, the team can update the documentation stating that it is only required 100_000 esLBR tokens (0.1% of the total LBR supply) delegated to meet the proposal threshold.

Assessed type

Math

#0 - c4-pre-sort

2023-07-08T13:34:16Z

JeffCX marked the issue as primary issue

#1 - c4-sponsor

2023-07-18T03:41:42Z

LybraFinance marked the issue as sponsor confirmed

#2 - c4-judge

2023-07-26T12:39:26Z

0xean marked the issue as satisfactory

#3 - c4-judge

2023-07-28T19:54:36Z

0xean marked the issue as selected for report

Findings Information

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
edited-by-warden
duplicate-268

Awards

29.0567 USDC - $29.06

External Links

Lines of code

https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/governance/LybraGovernance.sol#L143-L145

Vulnerability details

Impact

LybraGovernance contract only allows to vote during the first 3 blocks after the snapshot is taken. Assuming it takes roughly 12 seconds per 1 block on Ethereum, it is only 36 seconds to decide and vote for a proposal.

This would be too fast for a regular human being to evaluate the proposal and vote.

Proof of Concept

In LybraGovernance.sol, the clock() implements block number as a unit of time:

function clock() public override view returns (uint48){ return SafeCast.toUint48(block.number); }

LybraGovernance.sol#L161-L163 Openzeppelin#votingPeriod

According to the Lybra V2 Docs: "When a proposal is created, the community can cast their votes during a 7 day voting period. "

However, instead of 7 days, the current voting period has hardcoded 3 blocks:

function votingPeriod() public pure override returns (uint256){ return 3; }

LybraGovernance.sol#L147-L149 not allowing users enough time to evaluate the proposal and vote making the whole Governor contract useless.

Tools Used

Manual Review

Set the votingPeriod() to return 50400 blocks (604800 seconds in a day divided by 12 seconds (Approx. Ethereum Average Block Time) LybraGovernance.sol#L147-L149

Alternatively, change the unit of time to block.timestamp and set votingPeriod to 7 days, if it is intended to be used in different blockchain, due to the different time it takes for each block to be mined.

Assessed type

Timing

#0 - c4-pre-sort

2023-07-04T14:09:55Z

JeffCX marked the issue as duplicate of #268

#1 - c4-judge

2023-07-28T15:43:50Z

0xean marked the issue as satisfactory

#2 - c4-judge

2023-07-28T19:46:32Z

0xean changed the severity to 2 (Med Risk)

Findings Information

Labels

bug
2 (Med Risk)
satisfactory
duplicate-268

Awards

29.0567 USDC - $29.06

External Links

Lines of code

https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/governance/LybraGovernance.sol#L147-L149

Vulnerability details

Impact

Detailed description of the impact of this finding.

With a voting delay set to 1 block, users would not have enough time to buy tokens, or delegate their votes.

Proof of Concept

Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

In LybraGovernance.sol, the clock() implements block number as a unit of time:

function clock() public override view returns (uint48){ return SafeCast.toUint48(block.number); }

LybraGovernance.sol#L161-L163

This means, if votingDelay function returns 1, the delay since the proposal is submitted until voting power is fixed and voting starts equals 1 block. Openzeppelin#votingDelay

Tools Used

Manual Review

Set votingDelay to at least 7200 blocks (~1 day) Ethereum Average Block Time)

Assessed type

Timing

#0 - c4-pre-sort

2023-07-04T14:10:32Z

JeffCX marked the issue as duplicate of #268

#1 - c4-judge

2023-07-28T15:43:49Z

0xean marked the issue as satisfactory

Findings Information

🌟 Selected for report: T1MOH

Also found by: KupiaSec, RedTiger, devival, kenta, y51r

Labels

bug
2 (Med Risk)
high quality report
satisfactory
sponsor confirmed
edited-by-warden
duplicate-44

Awards

109.3508 USDC - $109.35

External Links

Lines of code

https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/configuration/LybraConfigurator.sol#L339 https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L128

Vulnerability details

Impact

The liquidation function in all contracts inheriting LybraPeUSDVaultBase.sol would revert until both vaultSafeCollateralRatio[pool] and vaultBadCollateralRatio[pool] are set. Even though, according to the documentation, the default safe and bad collateral ratios should be 160 and 150 respectively.

Contracts affected by LybraPeUSDVaultBase abstract: LybraWbETHVault.sol, LybraWstETHVault.sol, LybraRETHVault.sol.

Proof of Concept

In getBadCollateralRatio function in LybraConfigurator.sol, if vaultBadCollateralRatio[pool] equals 0, the function would return vaultSafeCollateralRatio[pool] - 1e19.

function getBadCollateralRatio(address pool) external view returns(uint256) { if(vaultBadCollateralRatio[pool] == 0) return vaultSafeCollateralRatio[pool] - 1e19; return vaultBadCollateralRatio[pool]; }

However, if vaultSafeCollateralRatio[pool] is not manually set through setSafeCollateralRatio function, it will equal 0, which will make vaultSafeCollateralRatio[pool] - 1e19 cause a revert due to the possible underflow.

LybraConfigurator.sol#L339

As a consequence, liquidation function in all contracts inheriting LybraPeUSDVaultBase.sol would revert after calling getBadCollateralRatio.

LybraPeUSDVaultBase.sol#L128

Tools Used

Manual Review

Replace vaultSafeCollateralRatio[pool] - 1e19 with 150 * 1e18 in getBadCollateralRatio function of LybraConfigurator.sol. LybraConfigurator.sol#L339

Assessed type

Under/Overflow

#0 - c4-pre-sort

2023-07-04T15:25:14Z

JeffCX marked the issue as low quality report

#1 - JeffCX

2023-07-04T15:25:33Z

If both vaultSafeCollateralRatio[pool] and vaultBadCollateralRatio[pool] in LybraConfigurator.sol are not set, getBadCollateralRatio(pool) would revert

this is expected, admin need to set the pool address

#2 - c4-pre-sort

2023-07-04T15:25:59Z

JeffCX marked the issue as high quality report

#3 - c4-pre-sort

2023-07-04T15:26:04Z

JeffCX marked the issue as primary issue

#4 - c4-sponsor

2023-07-18T06:25:26Z

LybraFinance marked the issue as sponsor confirmed

#5 - c4-judge

2023-07-26T12:40:06Z

0xean marked the issue as satisfactory

#6 - c4-judge

2023-07-28T19:56:04Z

0xean marked issue #44 as primary and marked this issue as a duplicate of 44

Awards

1.3247 USDC - $1.32

Labels

bug
2 (Med Risk)
downgraded by judge
high quality report
satisfactory
duplicate-27

External Links

Lines of code

https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/LybraWbETHVault.sol#L10

Vulnerability details

Impact

Detailed description of the impact of this finding.

Executing getAssetPrice() will always fail which makes all functions calling it fail as well. This includes depositEtherToMint which will not allow to deposit any funds to the contract.

Proof of Concept

Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

In depositEtherToMint of LybraWBETHVault.sol and LybraRETHVault.solthere is the line of code calling getAssetPrice():

_mintPeUSD(msg.sender, msg.sender, mintAmount, getAssetPrice());

LybraWbETHVault.sol#L28 LybraRETHVault.sol#L35 getAssetPrice() function calls nonexistent function exchangeRatio() in WBETH contract to get the exchange rate of WBETH. This will make the call to always fail.

function getAssetPrice() public override returns (uint256) { return (_etherPrice() * IWBETH(address(collateralAsset)).exchangeRatio()) / 1e18; }

LybraWbETHVault.sol#L35

Tools Used

Manual Review

Replace exchangeRatio() with exchangeRate() in the following instances: IWBETH interface LybraWbETHVault.sol#L10

getAssetPrice() function LybraWbETHVault.sol#L35

IRETH interface LybraRETHVault.sol#L10

getAssetPrice() function in LybraRETHVault.sol#L47

Assessed type

Error

#0 - c4-pre-sort

2023-07-04T02:31:49Z

JeffCX marked the issue as duplicate of #129

#1 - c4-pre-sort

2023-07-04T02:31:52Z

JeffCX marked the issue as low quality report

#2 - c4-pre-sort

2023-07-04T02:33:28Z

JeffCX marked the issue as high quality report

#3 - c4-pre-sort

2023-07-04T13:29:34Z

JeffCX marked the issue as duplicate of #27

#4 - c4-judge

2023-07-28T17:14:13Z

0xean changed the severity to 2 (Med Risk)

#5 - c4-judge

2023-07-28T17:15:07Z

0xean marked the issue as satisfactory

Awards

9.931 USDC - $9.93

Labels

bug
downgraded by judge
grade-b
QA (Quality Assurance)
duplicate-79
Q-02

External Links

Lines of code

https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/LybraWbETHVault.sol#L16

Vulnerability details

Impact

According to the developer’s comment in LybraWbETHVault.sol, the WBETH contract address is intended to be 0xae78736Cd615f374D3085123A210448E74Fc6393, which is rETH Token address. In case rETH Token address is passed in the LybraWbETHVault.sol constructor the contract will not be functional.

Proof of Concept

IWBETH interface has a deposit function function deposit(address referral) external payable; While there is no deposit function in rETH.

Tools Used

Manual Review

Use the correct WBETH Token address 0xa2E3356610840701BDf5611a53974510Ae27E2e1

Assessed type

Other

#0 - c4-pre-sort

2023-07-08T14:19:57Z

JeffCX marked the issue as duplicate of #79

#1 - c4-judge

2023-07-25T19:36:27Z

0xean changed the severity to QA (Quality Assurance)

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