DelegateToken holder can withdraw before expiry. Even if the principalToken holder extends the time it is of no use.
And anyone can withdraw any delegateTokenId and send all the tokens to their address.
Proof of Concept
The withdraw function in the delegateToken.sol contract can be called before expiry:
as we can see in this function there is a line that checks :
StorageHelpers.revertInvalidWithdrawalConditions(delegateTokenInfo, accountOperator, delegateTokenId, delegateTokenHolder);
and if we look at this check:
If the block.timestamp is less than the readExpiry which means the it can be withdrawn it does further checks but if the block.timestamp is greater than the readExpiry which means the witdrawn time is not reached yet but still this will pass because it doesnt revert if the block.timestamp is greater than the readExpiry. Which basically means the expiry time is of no use.
And since this will pass and there is no check for the msg.sender anyone can steal any tokens from any delegateTokenId.
Tools Used
manual review.
Recommended Mitigation Steps
add revert if (block.timestamp < readExpiry(delegateTokenInfo, delegateTokenId).
Lines of code
https://github.com/code-423n4/2023-09-delegate/blob/main/src/libraries/DelegateTokenStorageHelpers.sol#L155-L168
Vulnerability details
Impact
DelegateToken holder can withdraw before expiry. Even if the principalToken holder extends the time it is of no use. And anyone can withdraw any delegateTokenId and send all the tokens to their address.
Proof of Concept
The
withdraw
function in thedelegateToken.sol
contract can be called before expiry:as we can see in this function there is a line that checks :
StorageHelpers.revertInvalidWithdrawalConditions(delegateTokenInfo, accountOperator, delegateTokenId, delegateTokenHolder);
and if we look at this check:If the
block.timestamp
is less than the readExpiry which means the it can be withdrawn it does further checks but if theblock.timestamp
is greater than the readExpiry which means the witdrawn time is not reached yet but still this will pass because it doesnt revert if theblock.timestamp
is greater than the readExpiry. Which basically means the expiry time is of no use. And since this will pass and there is no check for the msg.sender anyone can steal any tokens from any delegateTokenId.Tools Used
manual review.
Recommended Mitigation Steps
add revert if
(block.timestamp < readExpiry(delegateTokenInfo, delegateTokenId)
.Assessed type
Access Control