USER WILL SEND TRANSACTION GAS WHICH IS ONLY ENOUGH TO EXECUTE `StakedUSDeV2.unstake` FUNCTION SUCCESFULLY BUT NOT ENOUGH TO FULLY EXECUTE THE `silo.withdraw` THUS LOSING ALL USER FUNDS #720
The StakedUSDeV2.unstake function is used to claim the staking amount after the cooldown period has finished. The unstake function will reset the userCooldown.cooldownEnd and userCooldown.underlyingAmount parameters to 0 for the msg.sender once the cool down periods has elapsed and then will withdraw the underlying assets from the silo contract to the receiver, as shown below:
The silo.withdraw function will transfer the underlying assets to the receiver address using the ERC20.transfer function. But the issue here is that the silo.withdraw function does not revert during transfer function failure and neither does silo.withdraw return a boolean value indicating the success of the fund transfer to the calling StakedUSDeV2.unstake function.
The issue here is if the msg.sender unknowingly or mistakenly passes in a transaction gas amount which is just enough to execute the StakedUSDeV2.unstake function but does not have enough gas to fully complete the silo.withdraw external function execution, the owner of the underlying funds will lose all of his underlying funds. This is because the transaction did not have enough gas to transfer the funds to the receiver address since the silo.withdraw did not execute completely due to running out of transaction gas. But the 1/64th amount of gas was available to the calling function to complete the StakedUSDeV2.unstake thus completing the transaction and setting the userCooldown.cooldownEnd and userCooldown.underlyingAmount values to 0 thus making the fund owner to lose all of his underlying funds in the silo contract.
Proof of Concept
function withdraw(address to, uint256 amount) external onlyStakingVault {
USDE.transfer(to, amount);
}
Hence it is recommended to update the silo.withdraw function to return a boolean success value to the StakedUSDeV2.unstake function to indicate the successful execution of the silo.withdraw transaction. This boolean value should be evaluated in the StakedUSDeV2.unstake function and if the boolean value is false the transaction should revert.
The recommended modification changes to the code snippets are shown below:
USDeSilo.withdraw function :
function withdraw(address to, uint256 amount) external onlyStakingVault returns(bool) {
bool success = USDE.transfer(to, amount);
require(success, "Fund transfer failed");
return true;
}
Lines of code
https://github.com/code-423n4/2023-10-ethena/blob/main/contracts/USDeSilo.sol#L28-L30 https://github.com/code-423n4/2023-10-ethena/blob/main/contracts/StakedUSDeV2.sol#L82-L86
Vulnerability details
Impact
The
StakedUSDeV2.unstake
function is used to claim the staking amount after the cooldown period has finished. Theunstake
function will reset theuserCooldown.cooldownEnd
anduserCooldown.underlyingAmount
parameters to0
for themsg.sender
once the cool down periods has elapsed and then will withdraw the underlying assets from thesilo
contract to the receiver, as shown below:The
silo.withdraw
function will transfer the underlying assets to thereceiver
address using theERC20.transfer
function. But the issue here is that thesilo.withdraw
function does not revert duringtransfer
function failure and neither doessilo.withdraw
return a boolean value indicating the success of the fund transfer to the callingStakedUSDeV2.unstake
function.The issue here is if the
msg.sender
unknowingly or mistakenly passes in a transaction gas amount which is just enough to execute theStakedUSDeV2.unstake
function but does not have enough gas to fully complete thesilo.withdraw
external function execution, the owner of the underlying funds will lose all of his underlying funds. This is because the transaction did not have enough gas to transfer the funds to thereceiver
address since thesilo.withdraw
did not execute completely due to running out of transaction gas. But the 1/64th amount of gas was available to the calling function to complete theStakedUSDeV2.unstake
thus completing the transaction and setting theuserCooldown.cooldownEnd
anduserCooldown.underlyingAmount
values to0
thus making the fund owner to lose all of his underlying funds in thesilo
contract.Proof of Concept
https://github.com/code-423n4/2023-10-ethena/blob/main/contracts/USDeSilo.sol#L28-L30
https://github.com/code-423n4/2023-10-ethena/blob/main/contracts/StakedUSDeV2.sol#L82-L86
Tools Used
Manual Review and VSCode
Recommended Mitigation Steps
Hence it is recommended to update the
silo.withdraw
function to return a boolean success value to theStakedUSDeV2.unstake
function to indicate the successful execution of thesilo.withdraw
transaction. This boolean value should be evaluated in theStakedUSDeV2.unstake
function and if the boolean value isfalse
the transaction should revert.The recommended modification changes to the code snippets are shown below:
USDeSilo.withdraw function :
StakedUSDeV2.unstake function :
Assessed type
Other