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: 33/147
Findings: 2
Award: $166.32
🌟 Selected for report: 0
🚀 Solo Findings: 0
161.7958 USDC - $161.80
https://github.com/code-423n4/2023-10-ethena/blob/main/contracts/StakedUSDe.sol#L203 https://github.com/code-423n4/2023-10-ethena/blob/main/contracts/StakedUSDe.sol#L225
The SOFT_RESTRICTED_STAKER_ROLE
is meant for users in countries where Ethena protocol cannot provide yield. These users are soft-restricted, preventing them from depositing USDe to get stUSDe or withdrawing stUSDe for USDe.
However, there is a significant oversight in the code. It doesn't prevent these users from withdrawing stUSDe for USDe. Consequently, a user who initially deposited in the StakedUSDeV2
vault and was later blacklisted can still earn yield and withdraw it as if they were a normal user, contradicting Ethena's legal requirements:
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.
Here's a coded PoC that demonstrates the issue:
function testSoftRestrictedEarnAndWithdraw() public { address USA_USER = makeAddr("USA_USER"); vm.label(USA_USER, "USA_USER"); vm.prank(owner); stakedUSDe.grantRole(BLACKLIST_MANAGER_ROLE, alice); assertTrue(stakedUSDe.hasRole(BLACKLIST_MANAGER_ROLE, alice)); _mintApproveDeposit(USA_USER, 100 ether); console.log("User balance balance:", usdeToken.balanceOf(USA_USER)); vm.prank(alice); stakedUSDe.addToBlacklist(USA_USER, false); assertTrue(stakedUSDe.hasRole(SOFT_RESTRICTED_STAKER_ROLE, USA_USER)); _transferRewards(1000 ether, 1000 ether); vm.warp(block.timestamp + 8 hours); _redeem(USA_USER, stakedUSDe.balanceOf(USA_USER)); console.log("User balance After:", usdeToken.balanceOf(USA_USER)); }
forge test --mc StakedUSDeV2CooldownDisabledTest --mt testSoftRestrictedEarnAndWithdraw -vvv
[!NOTE]
Users can stay in the system indefinitely and continue earning yield.
Manual review
To fix this issue, consider implementing the following changes:
SOFT_RESTRICTED_STAKER_ROLE
from withdrawing stUSDe for USDe.Other
#0 - c4-pre-sort
2023-10-31T15:47:50Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-10-31T15:48:06Z
raymondfam marked the issue as duplicate of #52
#2 - c4-judge
2023-11-10T21:41:03Z
fatherGoose1 marked the issue as satisfactory
🌟 Selected for report: 0xmystery
Also found by: 0x11singh99, 0xAadi, 0xAlix2, 0xG0P1, 0xStalin, 0xWaitress, 0x_Scar, 0xhacksmithh, 0xhunter, 0xpiken, Al-Qa-qa, Arz, Avci, Bauchibred, BeliSesir, Breeje, Bughunter101, DarkTower, Eeyore, Fitro, HChang26, Imlazy0ne, J4X, JCK, Kaysoft, Kral01, Madalad, Mike_Bello90, Noro, PASCAL, PENGUN, Proxy, Rickard, Shubham, SovaSlava, Strausses, Team_Rocket, ThreeSigma, Topmark, Udsen, Walter, Yanchuan, Zach_166, ZanyBonzy, adam-idarrha, adeolu, almurhasan, arjun16, ast3ros, asui, ayden, btk, cartlex_, castle_chain, cccz, chainsnake, codynhat, critical-or-high, cryptonue, csanuragjain, deepkin, degensec, dirk_y, erebus, foxb868, ge6a, hunter_w3b, jasonxiale, kkkmmmsk, lanrebayode77, lsaudit, marchev, matrix_0wl, max10afternoon, nuthan2x, oakcobalt, oxchsyston, pavankv, peanuts, pep7siup, pipidu83, pontifex, ptsanev, qpzm, radev_sw, rokinot, rotcivegaf, rvierdiiev, sorrynotsorry, squeaky_cactus, supersizer0x, tnquanghuy0512, twcctop, twicek, young, zhaojie, ziyou-
4.5226 USDC - $4.52
https://github.com/code-423n4/2023-10-ethena/blob/main/contracts/StakedUSDe.sol#L56-L59
The BLACKLIST_MANAGER_ROLE
has the ability to add/remove full/soft restrict any holder of stUSDe using addToBlacklist()
and removeFromBlacklist()
, both functions are safeguarded by the notOwner()
modifier to ensure that the blacklist target is not the owner()
.
However, there's an edge case where the BLACKLIST_MANAGER_ROLE
can still restrict the owner. This can happen by blacklisting the pending admin in the process of an ownership transfer.
[!NOTE]
Dev team confirmed: If it blacklists the owner, while it's not a concern it does go against the design of the smart contract. it will be a valid finding.
Here's a coded PoC that demonstrates the issue:
function testBlacklistAdmin() public { vm.prank(owner); stakedUSDe.grantRole(BLACKLIST_MANAGER_ROLE, alice); assertTrue(stakedUSDe.hasRole(BLACKLIST_MANAGER_ROLE, alice)); address pendingAdmin = makeAddr("pendingAdmin"); vm.label(pendingAdmin, "pendingAdmin"); vm.prank(owner); stakedUSDe.transferAdmin(pendingAdmin); // BLACKLIST_MANAGER_ROLE will blacklist the owner before he accept the ownership // It may happen intentionally(frontrunning acceptAdmin() or backrunning transferAdmin()) or mistakenly uint256 snapshot = vm.snapshot(); // Alice will soft restrict the owner vm.prank(alice); stakedUSDe.addToBlacklist(pendingAdmin, false); assertTrue( stakedUSDe.hasRole(SOFT_RESTRICTED_STAKER_ROLE, pendingAdmin) ); vm.prank(pendingAdmin); stakedUSDe.acceptAdmin(); // BLACKLIST_MANAGER_ROLE succesfully blacklisted the owner console.log( "Is owner soft restricted!", stakedUSDe.hasRole(SOFT_RESTRICTED_STAKER_ROLE, stakedUSDe.owner()) ); vm.revertTo(snapshot); // Alice will full restrict the owner vm.prank(alice); stakedUSDe.addToBlacklist(pendingAdmin, true); assertTrue( stakedUSDe.hasRole(FULL_RESTRICTED_STAKER_ROLE, pendingAdmin) ); vm.prank(pendingAdmin); stakedUSDe.acceptAdmin(); // BLACKLIST_MANAGER_ROLE succesfully blacklisted the owner console.log( "Is owner full restricted!", stakedUSDe.hasRole(FULL_RESTRICTED_STAKER_ROLE, stakedUSDe.owner()) ); }
StakedUSDeBlacklistTest
forge test --mc StakedUSDeBlacklistTest --mt testBlacklistAdmin -vvv
Manual review
To address this issue, add the following function to SingleAdminAccessControl
:
function pendingOwner() public view virtual returns (address) { return _pendingDefaultAdmin; }
Then, update the notOwner()
modifier as follows:
modifier notOwner(address target) { if (target == owner() || target == pendingOwner()) revert CantBlacklistOwner(); _; }
Other
#0 - c4-pre-sort
2023-10-31T15:40:58Z
raymondfam marked the issue as low quality report
#1 - c4-pre-sort
2023-10-31T15:41:03Z
raymondfam marked the issue as primary issue
#2 - c4-pre-sort
2023-10-31T15:52:50Z
raymondfam marked the issue as sufficient quality report
#3 - c4-sponsor
2023-11-09T18:05:29Z
FJ-Riveros (sponsor) acknowledged
#4 - c4-judge
2023-11-14T16:29:36Z
fatherGoose1 changed the severity to QA (Quality Assurance)
#5 - fatherGoose1
2023-11-14T16:30:09Z
QA. While this could happen, it would have to be a mistake by the admins. And it is easily fixed by a single call to revert the changes.
#6 - c4-judge
2023-11-14T16:30:19Z
fatherGoose1 marked the issue as grade-b