Platform: Code4rena
Start Date: 24/10/2023
Pot Size: $36,500 USDC
Total HM: 4
Participants: 147
Period: 6 days
Judge: 0xDjango
Id: 299
League: ETH
Rank: 39/147
Findings: 1
Award: $161.80
🌟 Selected for report: 0
🚀 Solo Findings: 0
161.7958 USDC - $161.80
Detailed description of the impact of this finding. A Soft_BlackListed User can fully redeem stUSDe tokens which contradicts the documentations.Quoted "Due to legal requirements, there's a SOFT_RESTRICTED_STAKER_ROLE and FULL_RESTRICTED_STAKER_ROLE. The former is for addresses based in countries we are not allowed to provide yield to, for example USA. Addresses under this category will be soft restricted. They cannot deposit USDe to get stUSDe or withdraw stUSDe for USDe. However they can participate in earning yield by buying and selling stUSDe on the open market." A SoftBlackListed user isn't allowed to deposit usde to the Staked USDe contract to earn yields,while this invariant is true, a SoftBlackListed user could equally redeem stUSDe tokens gotten from the free market in the stUSDe contract which completely defeats the purpose of the SOFTBLACKLISTED role.This bug allows SOFTBLACKLISTED USERS TO get tokens from the free market and earn yield even though they are not allowed to. ##Attack Flow: 1).A SoftBlacklisted User gets stUSDe from the free market. 2).Calls redeem || withdraw to get USDe tokens plus yields CallFlow goes like this: ##Restricted User calls redeem with (shares,receiver,user) -StakedUSDeV2.sol function redeem(uint256 shares, address receiver, address owner) public virtual override ensureCooldownOff returns (uint256) { return super.redeem(shares, receiver, owner); } -ERC4626.sol function redeem(uint256 shares, address receiver, address owner) public virtual override returns (uint256) { require(shares <= maxRedeem(owner), "ERC4626: redeem more than max");
uint256 assets = previewRedeem(shares); _withdraw(_msgSender(), receiver, owner, assets, shares); return assets; }
##Calls the _withdraw() which checks for only FULL_RESTRICTED_STAKERS -StakedUSDe.sol function _withdraw(address caller, address receiver, address _owner, uint256 assets, uint256 shares) internal override nonReentrant notZero(assets) notZero(shares) { if (hasRole(FULL_RESTRICTED_STAKER_ROLE, caller) || hasRole(FULL_RESTRICTED_STAKER_ROLE, receiver)) { revert OperationNotAllowed(); }
super._withdraw(caller, receiver, _owner, assets, shares); _checkMinShares();
} -ERC4626.sol function _withdraw( address caller, address receiver, address owner, uint256 assets, uint256 shares ) internal virtual { if (caller != owner) { _spendAllowance(owner, caller, shares); } _burn(owner, shares); SafeERC20.safeTransfer(_asset, receiver, assets);
emit Withdraw(caller, receiver, owner, assets, shares); }
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
if (hasRole(SOFT_RESTRICTED_STAKER_ROLE, caller) || hasRole(SOFT_RESTRICTED_STAKER_ROLE, receiver)) { revert OperationNotAllowed(); }
Other
#0 - c4-pre-sort
2023-11-01T00:35:25Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-11-01T00:35:43Z
raymondfam marked the issue as duplicate of #52
#2 - c4-judge
2023-11-10T21:43:37Z
fatherGoose1 marked the issue as satisfactory
#3 - c4-judge
2023-11-14T17:21:24Z
fatherGoose1 changed the severity to 2 (Med Risk)