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: 51/132
Findings: 2
Award: $155.96
š Selected for report: 0
š Solo Findings: 0
143.4901 USDC - $143.49
https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/token/PeUSDMainnetStableVision.sol#L26 https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/token/PeUSDMainnetStableVision.sol#L132
In PeUSDMainnetStableVision.sol executeFlashloan().
File: contracts/lybra/token/PeUSDMainnetStableVision.sol 129 function executeFlashloan(FlashBorrower receiver, uint256 eusdAmount, bytes calldata data) public payable { 130 uint256 shareAmount = EUSD.getSharesByMintedEUSD(eusdAmount); 131 EUSD.transferShares(address(receiver), shareAmount); 132 receiver.onFlashLoan(shareAmount, data); 133 bool success = EUSD.transferFrom(address(receiver), address(this), EUSD.getMintedEUSDByShares(shareAmount)); 134 require(success, "TF"); 135 136 uint256 burnShare = getFee(shareAmount); 137 EUSD.burnShares(address(receiver), burnShare); 138 emit Flashloaned(receiver, eusdAmount, burnShare); 139 }
In PeUSDMainnetStableVision.sol executeFlashloan() function doesn't pass the initiator. That makes it more difficult to integrate the executeFlashloan functionality.
As per eip-3156,
A initiator will often be required in the onFlashLoan function, which the lender knows as msg.sender. An alternative implementation which would embed the initiator in the data parameter by the caller would require an additional mechanism for the receiver to verify its accuracy, and is not advisable.
As per eip-3156,
Any receiver that keeps an approval for a given lender needs to include in onFlashLoan a mechanism to verify that the initiator is trusted.
As per eip-3156. This is reference implementation in which initiator is authenticated.
/// @dev ERC-3156 Flash loan callback function onFlashLoan( address initiator, address token, uint256 amount, uint256 fee, bytes calldata data ) external override returns(bytes32) { require( msg.sender == address(lender), "FlashBorrower: Untrusted lender" ); require( initiator == address(this), "FlashBorrower: Untrusted loan initiator" ); (Action action) = abi.decode(data, (Action)); if (action == Action.NORMAL) { // do one thing } else if (action == Action.OTHER) { // do another } return keccak256("ERC3156FlashBorrower.onFlashLoan"); }
Refer eip-3156 for reference.
Manual review
Add the initiator authentication and follow eip-3156.
Other
#0 - c4-pre-sort
2023-07-04T14:00:33Z
JeffCX marked the issue as duplicate of #280
#1 - c4-judge
2023-07-28T15:29:32Z
0xean marked the issue as satisfactory
#2 - c4-judge
2023-07-28T19:53:22Z
0xean changed the severity to 3 (High Risk)
š Selected for report: JCN
Also found by: 0xAnah, DavidGiladi, MohammedRizwan, Rageur, Raihan, ReyAdmirado, Rolezn, SAAJ, SAQ, SM3_SS, Sathish9098, ayo_dev, dharma09, fatherOfBlocks, hunter_w3b, mgf15, mrudenko, naman1778, shamsulhaq123, souilos, turvy_fuzz
12.4743 USDC - $12.47
Issue | Instances | ||
---|---|---|---|
[Gā01] | Save gas by preventing zero amount in mint() and burn() | 2 | |
[Gā02] | Catch function parameters as local variables to save gas | 1 | |
[Gā03] | Use nested-if and avoid "&&" to save gas | 2 | |
[Gā04] | Catch proposalId in LybraGovernance.sol within function | 3 |
In esLBR.sol, L-30 to L-43. There are 2 instances of this issue: Link to code
File: contracts/lybra/token/esLBR.sol function mint(address user, uint256 amount) external returns (bool) { + require(amount != 0, "invalid amount); require(configurator.tokenMiner(msg.sender), "not authorized"); require(totalSupply() + amount <= maxSupply, "exceeding the maximum supply quantity."); try IProtocolRewardsPool(configurator.getProtocolRewardsPool()).refreshReward(user) {} catch {} _mint(user, amount); return true; } function burn(address user, uint256 amount) external returns (bool) { + require(amount != 0, "invalid amount); require(configurator.tokenMiner(msg.sender), "not authorized"); try IProtocolRewardsPool(configurator.getProtocolRewardsPool()).refreshReward(user) {} catch {} _burn(user, amount); return true; }
Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read.
In esLBRBoost.sol, L-57 to L-69 There is 1 instance of this issue: Link to code
File: contracts/lybra/miner/esLBRBoost.sol function getUserBoost(address user, uint256 userUpdatedAt, uint256 finishAt) external view returns (uint256) { + address _user = user; + uint256 _userUpdatedAt = userUpdatedAt; + uint256 _finishAt = finishAt; - uint256 boostEndTime = userLockStatus[user].unlockTime; + uint256 boostEndTime = userLockStatus[_user].unlockTime; - uint256 maxBoost = userLockStatus[user].miningBoost; + uint256 maxBoost = userLockStatus[_user].miningBoost; - if (userUpdatedAt >= boostEndTime || userUpdatedAt >= finishAt) { + if (_userUpdatedAt >= boostEndTime || _userUpdatedAt >= _finishAt) { return 0; } - if (finishAt <= boostEndTime || block.timestamp <= boostEndTime) { + if (_finishAt <= boostEndTime || block.timestamp <= boostEndTime) { return maxBoost; } else { - uint256 time = block.timestamp > finishAt ? finishAt : block.timestamp; + uint256 time = block.timestamp > _finishAt ? _finishAt : block.timestamp; - return ((boostEndTime - userUpdatedAt) * maxBoost) / (time - userUpdatedAt); + return ((boostEndTime - _userUpdatedAt) * maxBoost) / (time - _userUpdatedAt); } }
In LBR.sol at L-64, There is 1 instance of this issue: Link to code
Catch the proposalId as a local variable in _quorumReached(), _voteSucceeded(), _countVote() to save Gwarmaccess (100 gas). In LybraGovernance.sol at L-59 to L-76. There is 2 instance of this issue: Link to code
In PeUSDMainnetStableVision.sol at L-199 Link to code
#0 - c4-sponsor
2023-07-27T06:58:50Z
LybraFinance marked the issue as sponsor acknowledged
#1 - c4-judge
2023-07-27T23:41:55Z
0xean marked the issue as grade-b