Skip to content

Latest commit

 

History

History
104 lines (71 loc) · 3.45 KB

File metadata and controls

104 lines (71 loc) · 3.45 KB

Basic Lilac Marmot

High

Token duplication verification failure will enable double withdrawals

Summary

Summary

function debondAndLock(address _pod, uint256 _amount) external nonReentrant {
  require(_amount > 0, "D1");
  require(_pod != address(0), "D2");
	
  IERC20(_pod).safeTransferFrom(_msgSender(), address(this), _amount);

  IDecentralizedIndex _podContract = IDecentralizedIndex(_pod);
  IDecentralizedIndex.IndexAssetInfo[] memory _podTokens = _podContract.getAllAssets();
  address[] memory _tokens = new address[](_podTokens.length);
  uint256[] memory _balancesBefore = new uint256[](_tokens.length);

  **// Get token addresses and balances before debonding
  for (uint256 i = 0; i < _tokens.length; i++) {
      _tokens[i] = _podTokens[i].token;
      _balancesBefore[i] = IERC20(_tokens[i]).balanceOf(address(this));
  }
  _podContract.debond(_amount, new address[](0), new uint8[](0));

  uint256[] memory _receivedAmounts = new uint256[](_tokens.length);
  for (uint256 i = 0; i < _tokens.length; i++) {
      _receivedAmounts[i] = IERC20(_tokens[i]).balanceOf(address(this)) - _balancesBefore[i];
  }**
	...
}

The debondAndLock function is designed to deposit a specific token. Since the _pod parameter can have an arbitrary address, the return value of the getAllAssets function can also vary depending on the address. If the getAllAssets function returns duplicate addresses, a single deposit could result in multiple values for _receivedAmounts. As a result, the deposited amount could be withdrawn multiple times, creating a vulnerability that allows multiple withdrawals from a single deposit.

function _withdraw(address _user, uint256 _lockId) internal {
  LockInfo storage _lock = locks[_lockId];
  require(_lock.user == _user, "W1");
  require(!_lock.withdrawn, "W2");
  require(block.timestamp >= _lock.unlockTime, "W3");

  _lock.withdrawn = true;

  for (uint256 i = 0; i < _lock.tokens.length; i++) {
      if (_lock.amounts[i] > 0) {
          **IERC20(_lock.tokens[i]).safeTransfer(_user, _lock.amounts[i]);**
      }
  }

  emit TokensWithdrawn(_lockId, _user, _lock.tokens, _lock.amounts);
}

Since there is no validation for duplicate addresses, as described above, it becomes possible to make multiple withdrawals.

Root Cause

in PodUnwrapLocker.sol:74 there is a missing check on token duplication

Internal Pre-conditions

No response

External Pre-conditions

No response

Attack Path

  1. Deploy a contract that implements the transferFrom, getAllAssets, and debond functions.
  2. Call the debondAndLock function and set the _pod parameter to the address of the contract deployed in step 1.
  3. Withdraw the duplicate tokens through the earlyWithdraw function.

Impact

The affected party will lose all assets.

PoC

No response

Mitigation

// Get token addresses and balances before debonding
for (uint256 i = 0; i < _tokens.length; i++) {
  _tokens[i] = _podTokens[i].token;
  **for(uint256 j=i+1; j<_tokens.length; j++) require(_tokens[i] != _tokens[j], "invalid same token");**
  _balancesBefore[i] = IERC20(_tokens[i]).balanceOf(address(this));
}

performs checks to verify the token quantity and ensure that the same token is being used.

Refernces

https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/PodUnwrapLocker.sol#L72-L75

https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/PodUnwrapLocker.sol#L158