code-423n4 / 2022-04-jpegd-findings

1 stars 1 forks source link

Instead of `call()`, `transfer()` is used for the withdraw mechanism #234

Closed code423n4 closed 2 years ago

code423n4 commented 2 years ago

Lines of code

https://github.com/code-423n4/2022-04-jpegd/blob/main/contracts/vaults/FungibleAssetVaultForDAO.sol#L193-L206

Vulnerability details

This is similar to a previous Code4rena issue: https://github.com/code-423n4/2021-04-meebits-findings/issues/2

POC

See @audit:

File: FungibleAssetVaultForDAO.sol
193:     function withdraw(uint256 amount) external onlyRole(WHITELISTED_ROLE) nonReentrant {
194:         require(amount > 0 && amount <= collateralAmount, "invalid_amount");
195: 
196:         uint256 creditLimit = getCreditLimit(collateralAmount - amount);
197:         require(creditLimit >= debtAmount, "insufficient_credit");
198: 
199:         collateralAmount -= amount;
200: 
201:         if (collateralAsset == ETH) payable(msg.sender).transfer(amount); //@audit medium risk: might fail
202:         else
203:             IERC20Upgradeable(collateralAsset).safeTransfer(msg.sender, amount);
204: 
205:         emit Withdraw(msg.sender, amount);
206:     }

For the withdraw logic, this function uses the deprecated transfer() function on an address. This transaction will fail inevitably when:

  1. The claimer smart contract does not implement a payable function.
  2. The claimer smart contract does implement a payable fallback which uses more than 2300 gas unit.
  3. The claimer smart contract implements a payable fallback function that needs less than 2300 gas units but is called through proxy, raising the call's gas usage above 2300.

Additionally, using higher than 2300 gas might be mandatory for some multisig wallets.

I recommend using call() instead of transfer()

spaghettieth commented 2 years ago

Duplicate of #39

dmvt commented 2 years ago

See comment on #39. This is a QA issue. This is not the same as the meebits issue.