Platform: Code4rena
Start Date: 05/10/2023
Pot Size: $33,050 USDC
Total HM: 1
Participants: 54
Period: 6 days
Judge: hansfriese
Id: 294
League: ETH
Rank: 8/54
Findings: 1
Award: $1,774.19
🌟 Selected for report: 0
🚀 Solo Findings: 0
1774.1935 USDC - $1,774.19
NOTE: In bot report this was added into disputed issue due to reasoning where the bot assumed that the this contract will only wrap the ens token which does not return false on failure. But as the sponsor confirmed this contract should wrap and work with any token
The return value is not checked for the safeTransferFrom that could lead to following problems if the token being used does not revert on failure and instead returns the false. Transfer can fail due to any reason, lack of funds, contract like USDT is paused and is such cases following scenerios may happen:
If the user is transferring the tokens from source proxy to target proxy, funds may not be transferred but nfts will be minted and burnt leading to unclaimaible assets in one proxy and can be claimed from other proxy even though nothing was transferred leading to loss of funds for the users.
If the user is getting the reimburse and the transferfFrom fails, nothing is transferred and nfts are burned so tokens remainned stuck in the contract.
If the user is transferring to the proxy, transferFrom may fail, transfer never happen and nfts get minted to user leading to user be able to steal funds in future with those nfts from the proxies.
First look at the following code snippet from the delegateMulti
function, that calls three main functions where the transfers happen:
if (transferIndex < Math.min(sourcesLength, targetsLength)) { // require(source != target, "Delegate: Source and target must be different"); // Process the delegation transfer between the current source and target delegate pair. _processDelegation(source, target, amount); // if source is 1 than this can happen as there is no target, so no target means that we will reimburse the amount to the source. } else if (transferIndex < sourcesLength) { // Handle any remaining source amounts after the transfer process. _reimburse(source, amount); // if there is no source but there is a target, then we will create a proxy delegator and transfer the amount to the target } else if (transferIndex < targetsLength) { // Handle any remaining target amounts after the transfer process. createProxyDelegatorAndTransfer(target, amount); }
So now lets have a look at the indivisual functions, firstly reimburse
as it is simple and easy to understand
So if we have a source and no target relative to it, it means user wants to get the amount back from the proxy address derived from the source passed:
function _reimburse(address source, uint256 amount) internal { address proxyAddressFrom = retrieveProxyContractAddress(token, source); token.transferFrom(proxyAddressFrom, msg.sender, amount); }
Lets say the transferFrom here fails :
So than on the following line nfts will be burnt as the sourceLength is > 0
So essentially, nothing is transferred back to the msg.sender but his NFTs are burnt so the funds are permanently locked in the proxy now.
Not lets have a look at the _processDelegation
function:
function _processDelegation( address source, address target, uint256 amount ) internal { uint256 balance = getBalanceForDelegate(source); assert(amount <= balance); deployProxyDelegatorIfNeeded(target); transferBetweenDelegators(source, target, amount); emit DelegationProcessed(source, target, amount); }
This function after making the necessary checks call the transferBetweenDelegators
which does the following:
function transferBetweenDelegators( address from, address to, uint256 amount ) internal { address proxyAddressFrom = retrieveProxyContractAddress(token, from); address proxyAddressTo = retrieveProxyContractAddress(token, to); token.transferFrom(proxyAddressFrom, proxyAddressTo, amount); }
So it derives the proxy address and transfer between them, so if transferFrom fails here following will happen:
Which essentially means from the target more can be claimed than there actually is, and the left in sources can be claimed no more. Loss of funds for the last mover.
Third scenerio is where there is no source only target for a particular amount, in that case createProxyDelegatorAndTransfer
is called which essentially transfers the tokens from the msg.sender into proxy address derived from the target address:
function createProxyDelegatorAndTransfer( address target, uint256 amount ) internal { address proxyAddress = deployProxyDelegatorIfNeeded(target); token.transferFrom(msg.sender, proxyAddress, amount); }
If the transferFrom fails here, nothing will be transferred into proxy, but the nfts will be minted to msg.sender on the following line:
So now the user can claim the tokens from the proxy if it is already deployed and some one else have delegated to the same target already. With these nfts those tokens be stolen.
Manual review
Use the safeErc20 library.
ERC20
#0 - c4-pre-sort
2023-10-13T04:13:46Z
141345 marked the issue as low quality report
#1 - c4-pre-sort
2023-10-13T04:15:21Z
141345 marked the issue as remove high or low quality report
#2 - c4-pre-sort
2023-10-13T04:15:29Z
141345 marked the issue as duplicate of #91
#3 - c4-pre-sort
2023-10-13T04:15:34Z
141345 marked the issue as sufficient quality report
#4 - c4-judge
2023-10-24T17:11:17Z
hansfriese marked the issue as satisfactory
#5 - c4-judge
2023-10-28T06:23:43Z
hansfriese marked the issue as duplicate of #90
#6 - c4-judge
2023-10-28T06:29:23Z
hansfriese marked the issue as not a duplicate
#7 - c4-judge
2023-10-28T06:29:28Z
hansfriese changed the severity to 3 (High Risk)
#8 - c4-judge
2023-10-28T06:29:38Z
hansfriese marked the issue as duplicate of #91
#9 - c4-judge
2023-10-29T10:13:32Z
hansfriese changed the severity to 2 (Med Risk)