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: 34/132
Findings: 2
Award: $291.12
🌟 Selected for report: 0
🚀 Solo Findings: 0
281.1877 USDC - $281.19
The EUSD.sol contract is vulnerable to "ERC4626 inflation attack".
The EUSD.sol contract is susceptible to an underlying balance manipulation attack
When a user performs mint
operation, the amount of shares they receive is calculated by taking the number of assets provided, multiplying that by the number of existing shares in circulation and then dividing the result by all assets owned by the smart contract vault.
The attack is carried out by manipulating the totalShares
by minting a small amount of shares and fruntrunning the next user that mints.
function getMintedEUSDByShares(uint256 _sharesAmount) public view returns (uint256) { if (_totalShares == 0) { return 0; } else { return _sharesAmount.mul(_totalSupply).div(_totalShares); } }
Attack Scenario:
see further examples of this attack:
Manual Review
Use Openzeppelin's implementation of ERC4626: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/8177c4620e049b2749c2069651d7d5b4691e23d2/contracts/token/ERC20/extensions/ERC4626.sol
ERC4626
#0 - c4-pre-sort
2023-07-08T20:21:52Z
JeffCX marked the issue as duplicate of #106
#1 - c4-judge
2023-07-28T15:32:09Z
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
9.931 USDC - $9.93
_countVote
function to remove the unnamed parameter.The last parameter of _countVote
function in LibraGovernance.sol is unnamed.
Refactor code to remove unnamed parameter.
function _countVote(uint256 proposalId, address account, uint8 support, uint256 weight, bytes memory) internal override { require(state(proposalId) == ProposalState.Active, "GovernorBravo::castVoteInternal: voting is closed"); require(support <= 2, "GovernorBravo::castVoteInternal: invalid vote type"); ProposalExtraData storage proposalExtraData = proposalData[proposalId]; Receipt storage receipt = proposalExtraData.receipts[account]; require(receipt.hasVoted == false, "GovernorBravo::castVoteInternal: voter already voted"); proposalExtraData.supportVotes[support] += weight; receipt.hasVoted = true; receipt.support = support; receipt.votes = weight; proposalExtraData.totalVotes += weight; }
The proposalId
parameter of the _execute
function in LibraGovernance.sol is commented out. Refactor code to remove commented parameter.
function _execute(uint256 /* proposalId */, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash) internal virtual override { require(GovernanceTimelock.checkOnlyRole(keccak256("TIMELOCK"), msg.sender), "not authorized"); super._execute(1, targets, values, calldatas, descriptionHash); // _timelock.executeBatch{value: msg.value}(targets, values, calldatas, 0, descriptionHash);//@audit commented code. }
Remove commented code.
function _execute(uint256 /* proposalId */, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash) internal virtual override { require(GovernanceTimelock.checkOnlyRole(keccak256("TIMELOCK"), msg.sender), "not authorized"); super._execute(1, targets, values, calldatas, descriptionHash); // _timelock.executeBatch{value: msg.value}(targets, values, calldatas, 0, descriptionHash); }
The stakedLBRLpValue
function in the EUSDMinningIncentives.sol uses a hardcoded address. Do not hardcode addresses since the contract will be deployed to multiple network.
function stakedLBRLpValue(address user) public view returns (uint256) { uint256 totalLp = IEUSD(ethlbrLpToken).totalSupply(); (, int etherPrice, , , ) = etherPriceFeed.latestRoundData(); (, int lbrPrice, , , ) = lbrPriceFeed.latestRoundData(); uint256 etherInLp = (IEUSD(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2).balanceOf(ethlbrLpToken) * uint(etherPrice)) / 1e8; uint256 lbrInLp = (IEUSD(LBR).balanceOf(ethlbrLpToken) * uint(lbrPrice)) / 1e8; uint256 userStaked = IEUSD(ethlbrStakePool).balanceOf(user); return (userStaked * (lbrInLp + etherInLp)) / totalLp; }
The decimals
local variable shadows the ERC20.decimals state variable of the parent contract.
Use another local variable name in order to avoid variable shadowing. Files:
constructor(address _config, uint8 _sharedDecimals, address _lzEndpoint) ERC20("LBR", "LBR") BaseOFTV2(_sharedDecimals, _lzEndpoint) { configurator = Iconfigurator(_config); uint8 decimals = decimals(); //@audit shadowed variables - check for further attack. require(_sharedDecimals <= decimals, "OFT: sharedDecimals must be <= decimals"); ld2sdRatio = 10 ** (decimals - _sharedDecimals); }
Use a solidity version of at least 0.8.19 as latest stable versions will bug fixes and security updates. Files: most files.
#0 - c4-pre-sort
2023-07-27T19:44:52Z
JeffCX marked the issue as high quality report
#1 - c4-judge
2023-07-27T23:57:05Z
0xean marked the issue as grade-b
#2 - c4-sponsor
2023-07-29T10:30:09Z
LybraFinance marked the issue as sponsor acknowledged