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
Rank: 88/132
Findings: 2
Award: $59.22
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: bytes032
Also found by: 0xMAKEOUTHILL, 0xgrbr, 0xkazim, 0xnacho, Arz, Co0nan, CrypticShepherd, Cryptor, HE1M, Iurii3, LaScaloneta, LokiThe5th, LuchoLeonel1, MrPotatoMagic, Musaka, Qeew, RedTiger, SovaSlava, Toshii, Vagner, a3yip6, azhar, bart1e, devival, hl_, jnrlouis, kutugu, peanuts, pep7siup, qpzm, smaul
1.3247 USDC - $1.32
https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/LybraWbETHVault.sol#L34-L36 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/LybraWbETHVault.sol#L27-L29 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L65-L68 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L96-L100 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L160 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L217
Minting new peUSD
does not work on the LuybraWBETHVault
. This also impacts minting peUSD
triggered by the following vault methods (which throw error on calling):
depositEtherToMint
depositAssetToMint
mint
liquidation
rigidRedemption
withdraw
& _withdraw
.it("Should successfully deposit ether and mint peUSD", async function () { const wbethVaultContract = await ethers.getContractFactory("LybraWBETHVault"); const vault = await wbethVaultContract.deploy( // PeUSD mainnet "0x5Af2DF7639df6558907c20368969e851d266F9bC", // Eth oracle "0xF53Ac365e7ED2c6460D4FD878EDA61a6BD755B96", // wbeth mainnet address "0xa2E3356610840701BDf5611a53974510Ae27E2e1", // configurator address "0xaB3202Bff6361d926191Af648A32D5811EA3D991" ); try { console.log("Calling 'LybraWBETHVault#depositEtherToMint()'..."); await vault.depositEtherToMint(1, { value: ethers.utils.parseEther("2") }); } catch (err) { console.log("'LybraWBETHVault#depositEtherToMint()' errored out with the following error:\n", err); } try { console.log("Calling 'LybraWBETHVault#getAssetPrice()'..."); const price = await vault.getAssetPrice(); console.log("Price:", price); } catch (err) { console.log("'LybraWBETHVault#getAssetPrice()' errored out with the following error:\n", err); } });
The denial of service is caused by the incorrect implementation of the LybraWBETHVault#getAssetPrice()
method. In the method's implementation, the following call is executed to get the WBETH<>ETH
exchange ratio: IWBETH(address(collateralAsset)).exchangeRatio()
. However, exchangeRatio
method does not exist in the WBETH
contract.
function getAssetPrice() public override returns (uint256) { return (_etherPrice() * IWBETH(address(collateralAsset)).exchangeRatio()) / 1e18; }
This also means that the existing IWBETH
interface has the wrong declaration. Checking the WBETH token implementation contract, the correct method to call for getting the exchange rate is exchangeRate()
.
Manual review and hardhat
Update IWBETH
interface to:
interface IWBETH { function exchangeRate() external view returns (uint256); function deposit(address referral) external payable; }
Change the getAssetPrice()
function implementation to:
function getAssetPrice() public override returns (uint256) { return (_etherPrice() * IWBETH(address(collateralAsset)).exchangeRate()) / 1e18; }
DoS
#0 - c4-pre-sort
2023-07-09T01:54:04Z
JeffCX marked the issue as duplicate of #27
#1 - c4-judge
2023-07-28T17:14:12Z
0xean changed the severity to 2 (Med Risk)
#2 - c4-judge
2023-07-28T17:15:26Z
0xean marked the issue as satisfactory
🌟 Selected for report: bytes032
Also found by: 0xMAKEOUTHILL, 0xgrbr, 0xkazim, 0xnacho, Arz, Co0nan, CrypticShepherd, Cryptor, HE1M, Iurii3, LaScaloneta, LokiThe5th, LuchoLeonel1, MrPotatoMagic, Musaka, Qeew, RedTiger, SovaSlava, Toshii, Vagner, a3yip6, azhar, bart1e, devival, hl_, jnrlouis, kutugu, peanuts, pep7siup, qpzm, smaul
1.3247 USDC - $1.32
https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/LybraRETHVault.sol#L46-L48 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/LybraRETHVault.sol#L9-L11 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L65-L68 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L96-L100 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L160 https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L217
Minting new peUSD
does not work on the LuybraRETHVault
. This also impacts minting peUSD
triggered by the following vault methods (which throw error on calling):
depositEtherToMint
depositAssetToMint
mint
liquidation
rigidRedemption
withdraw
& _withdraw
.it("Should successfully deposit ether and mint peUSD", async function () { const rethVaultContract = await ethers.getContractFactory("LybraRETHVault"); const vault = await rethVaultContract.deploy( // PeUSD mainnet "0x5Af2DF7639df6558907c20368969e851d266F9bC", // configurator address "0xaB3202Bff6361d926191Af648A32D5811EA3D991", // reth mainnet address "0xae78736Cd615f374D3085123A210448E74Fc6393", // Eth oracle "0xF53Ac365e7ED2c6460D4FD878EDA61a6BD755B96", // rkpool mainnet address "0xDD3f50F8A6CafbE9b31a427582963f465E745AF8" ); try { console.log("\n\nCalling 'LybraRETHVault#depositEtherToMint()'..."); await vault.depositEtherToMint(1, { value: ethers.utils.parseEther("2") }); } catch (err) { console.log("'LybraRETHVault#depositEtherToMint()' errored out with the following error:\n", err); } try { console.log("\n\nCalling 'LybraRETHVault#getAssetPrice()'..."); const price = await vault.getAssetPrice(); console.log("Price:", price); } catch (err) { console.log("'LybraRETHVault#getAssetPrice()' errored out with the following error:\n", err); } });
The denial of service is caused by the incorrect implementation of the LybraRETHVault#getAssetPrice()
method. In the method's implementation, the following call is executed to get the rETH<>ETH
exchange ratio: IRETH(address(collateralAsset)).getExchangeRatio()
. However, getExchangeRatio()
method does not exist in the rETH
contract.
function getAssetPrice() public override returns (uint256) { return (_etherPrice() * IRETH(address(collateralAsset)).getExchangeRatio()) / 1e18; }
This also means that the existing IRETH
interface has the wrong declaration. Checking the rETH token implementation contract, the correct method to call for getting the exchange rate is getExchangeRate()
.
Manual review and hardhat
Update IRETH
interface to:
interface IRETH { function getExchangeRate() external view returns (uint256); }
Change the getAssetPrice()
function implementation to:
function getAssetPrice() public override returns (uint256) { return (_etherPrice() * IRETH(address(collateralAsset)).getExchangeRate()) / 1e18; } ## Assessed type DoS
#0 - c4-pre-sort
2023-07-04T13:25:55Z
JeffCX marked the issue as high quality report
#1 - c4-pre-sort
2023-07-04T13:26:02Z
JeffCX marked the issue as duplicate of #27
#2 - c4-judge
2023-07-28T17:14:12Z
0xean changed the severity to 2 (Med Risk)
#3 - c4-judge
2023-07-28T17:15:25Z
0xean marked the issue as satisfactory
🌟 Selected for report: 0xnev
Also found by: 0xRobocop, 0xbrett8571, 0xkazim, 0xnacho, 3agle, 8olidity, ABAIKUNANBAEV, Bauchibred, Co0nan, CrypticShepherd, D_Auditor, DelerRH, HE1M, Iurii3, Kaysoft, MrPotatoMagic, RedOneN, RedTiger, Rolezn, SanketKogekar, Sathish9098, Timenov, Toshii, Vagner, bart1e, bytes032, codetilda, devival, halden, hals, kutugu, m_Rassska, naman1778, nonseodion, seth_lawson, solsaver, squeaky_cactus, totomanov, y51r, yudan, zaevlad
57.9031 USDC - $57.90
The WBETH address specified in the LybraWBETHVault comments is misleading. The displayed address is actually the rETH
mainnet address (https://etherscan.io/token/0xae78736Cd615f374D3085123A210448E74Fc6393).
rkPool
variable is not valid on all the chainsThe address which is used to initialize the rkPool
variable is only valid on mainnet
, but it doesn't correspond to a RocketPool Deposit pool on Arbitrum
or other L2s.
https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/LybraRETHVault.sol#L18
IRkPool rkPool = IRkPool(0xDD3f50F8A6CafbE9b31a427582963f465E745AF8);
Therefore, removing the rkPool
initialization is highly recommended.
require(PeUSD.allowance(provider, address(this)) > 0, "provider should authorize to provide liquidation EUSD");
PeUSD.transferFrom
(https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L197-L204) in the _repay
function.transferFrom
method doesn't check for allowance if the vault is already a mint vault.LybraPeUSDVaultBase
is a mint vault, so the allowance is not checkedGiven these, the allowance check is redundant.
WithdrawAsset
- wrong params orderDeclaration:
event WithdrawAsset(address sponsor, address indexed onBehalfOf, address asset, uint256 amount, uint256 timestamp);
emit WithdrawAsset( _provider, address(collateralAsset), --| switched params _onBehalfOf, --| _amount, block.timestamp );
The correct emit statement:
emit WithdrawAsset( _provider, address(collateralAsset), _onBehalfOf, _amount, block.timestamp)
The WBETH#deposit
function allows users to deposit any ETH amount higher than 0:
/** * @dev Function to deposit eth to the contract for wBETH * @param referral The referral address */ function deposit(address referral) external payable { require(msg.value > 0, "zero ETH amount"); // msg.value and exchangeRate are all scaled by 1e18 uint256 wBETHUnit = 10 ** uint256(decimals); uint256 wBETHAmount = msg.value.mul(wBETHUnit).div(exchangeRate()); _mint(msg.sender, wBETHAmount); emit DepositEth(msg.sender, msg.value, wBETHAmount, referral); }
However, LybraWBETHVault#depositEtherToMint
doesn't allow ETH amount smaller than 1 ether, which doesn't serve users that want to deposit a smaller ETH amount even if there is WBETH support for doing this.
Examples:
require( collateralAsset.balanceOf(address(this)) >= preBalance + assetAmount, "" );
require(onBehalfOf != address(0), "TZA"); require(amount > 0, "ZA");
require(poolTotalPeUSDCirculation + _mintAmount <= configurator.mintVaultMaxSupply(address(this)), "ESL");
https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/LybraRETHVault.sol#L28
require(msg.value >= 1 ether, "DNL");
require(success, "TF");
There are multiple comments in the vaults code containing addresses to be used when deploying each smart contract. Although these addresses are helpful when deploying the vaults on the mainnet, they are misleading for the deployments on other L2s. This happens because of the addresses not matching their L2s equivalent addresses.
Examples:
// rkPool = 0xDD3f50F8A6CafbE9b31a427582963f465E745AF8 // rETH = 0xae78736Cd615f374D3085123A210448E74Fc6393
// stETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84 // oracle = 0x4c517D4e2C851CA76d7eC94B805269Df0f2201De
https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/LybraWbETHVault.sol#L16
//WBETH = 0xae78736Cd615f374D3085123A210448E74Fc6393
//WstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; //Lido = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
Recommendation: remove the comments containing the smart contract addresses. You can also keep a separate json file that would contain the addresses of each dependency, batched by chain. This could be used in the deployment and deployment verification processes.
Example of json:
{ [mainnet_chain_id]: { "rkPool": "0xDD3f50F8A6CafbE9b31a427582963f465E745AF8", "rETH": "0xae78736Cd615f374D3085123A210448E74Fc6393", "stETH": "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", "ethPriceOracle": "0x4c517D4e2C851CA76d7eC94B805269Df0f2201De", ... }, [arbitrum_chain_id]: { "rkPool": "<rkPool arbitrum address>", "rETH": "<rETH arbitrum address>", "stETH": "<stETH arbitrum address>", "ethPriceOracle": "<eth price oracle address>", ... }, ... }
FeeDistribution
event is not used in LybraPeUSDVaultBaseevent FeeDistribution( address indexed feeAddress, uint256 feeAmount, uint256 timestamp );
eUSD
is mentioned, but peUSD
should be mentionedExamples:
event LiquidationRecord( address provider, address keeper, address indexed onBehalfOf, uint256 eusdamount, ----> should be peUSD amount uint256 LiquidateAssetAmount, ----> should start with lower case letter uint256 keeperReward, bool superLiquidation, uint256 timestamp );
/** * @dev Get USD value of current collateral asset and minted EUSD through price oracle / Collateral asset USD value must higher than safe Collateral Ratio. */
#0 - c4-pre-sort
2023-07-27T20:58:18Z
JeffCX marked the issue as high quality report
#1 - c4-judge
2023-07-27T23:57:11Z
0xean marked the issue as grade-a
#2 - c4-sponsor
2023-07-29T10:33:10Z
LybraFinance marked the issue as sponsor confirmed