Algo Ssstablecoinsss

AI First Flight #2
Beginner FriendlyDeFi
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Reentrancy Vulnerability in Deposit and Redeem Functions

Root + Impact

Description

  • * The protocol allows users to deposit collateral tokens (WETH/WBTC) and redeem them. When depositing, the contract updates the user's collateral balance before transferring tokens. When redeeming, the contract updates the user's collateral balance before transferring tokens out.

    * The `_deposit_collateral()` and `_redeem_collateral()` functions update state variables before making external calls to ERC20 tokens. This violates the Checks-Effects-Interactions pattern and creates a reentrancy vulnerability where malicious ERC20 tokens can reenter the contract during transfer operations and exploit the updated state.

    ```vyper

    @internal

    def _deposit_collateral(

    token_collateral_address: address, amount_collateral: uint256

    ):

    assert amount_collateral > 0, "DSCEngine_NeedsMoreThanZero"

    assert self.token_address_to_price_feed[token_collateral_address] != empty(

    address

    ), "DSCEngine__TokenNotAllowed"

    self.user_to_token_address_to_amount_deposited[msg.sender][

    token_collateral_address

    ] += amount_collateral // @> State updated BEFORE external call

    log CollateralDeposited(msg.sender, amount_collateral)

    success: bool = extcall IERC20(token_collateral_address).transferFrom(

    msg.sender, self, amount_collateral // @> External call can reenter

    )

    assert success, "DSCEngine_TransferFailed"

    ```

    ```vyper

    @internal

    def _redeem_collateral(

    token_collateral_address: address,

    amount_collateral: uint256,

    _from: address,

    _to: address,

    ):

    self.user_to_token_address_to_amount_deposited[_from][

    token_collateral_address

    ] -= amount_collateral // @> State updated BEFORE external call

    log CollateralRedeemed(token_collateral_address, amount_collateral, _from, _to)

    success: bool = extcall IERC20(token_collateral_address).transfer(

    _to, amount_collateral // @> External call can reenter

    )

    assert success, "DSCEngine_TransferFailed"

    ```


Risk

Likelihood:

  • * Malicious ERC20 tokens can implement hooks in `transfer()` and `transferFrom()` that call back into the DSC Engine contract during the transfer operation

    * The protocol accepts any ERC20 token that has a price feed configured, and there's no validation that tokens are non-reentrant

Impact:

  • * Attackers can mint DSC tokens without actually depositing collateral by reentering during `transferFrom()` and using the updated state to pass health factor checks

    * Attackers can redeem more collateral than they deposited by reentering during `transfer()` and manipulating state before the transfer completes

    * Direct fund loss and protocol insolvency

Proof of Concept

```python
# Malicious ERC20 token contract
@external
def transferFrom(_from: address, _to: address, _value: uint256) -> bool:
# Reenter DSC Engine before completing transfer
if _to == DSC_ENGINE_ADDRESS:
# Call mint_dsc using updated state from deposit
DSC_ENGINE.mint_dsc(large_amount) # Passes health check due to updated collateral state
# Complete transfer
self.balances[_from] -= _value
self.balances[_to] += _value
return True
# Attack flow:
# 1. Attacker deploys malicious token and gets price feed added
# 2. Attacker calls deposit_collateral() with malicious token
# 3. State updated: user_to_token_address_to_amount_deposited[attacker][token] += amount
# 4. transferFrom() called, reenters and calls mint_dsc()
# 5. mint_dsc() sees updated collateral state and allows minting
# 6. transferFrom() completes
# 7. Attacker has DSC tokens without actually depositing collateral
```

Recommended Mitigation

```diff
@internal
def _deposit_collateral(
token_collateral_address: address, amount_collateral: uint256
):
assert amount_collateral > 0, "DSCEngine_NeedsMoreThanZero"
assert self.token_address_to_price_feed[token_collateral_address] != empty(
address
), "DSCEngine__TokenNotAllowed"
- self.user_to_token_address_to_amount_deposited[msg.sender][
- token_collateral_address
- ] += amount_collateral
- log CollateralDeposited(msg.sender, amount_collateral)
success: bool = extcall IERC20(token_collateral_address).transferFrom(
msg.sender, self, amount_collateral
)
assert success, "DSCEngine_TransferFailed"
+ self.user_to_token_address_to_amount_deposited[msg.sender][
+ token_collateral_address
+ ] += amount_collateral
+ log CollateralDeposited(msg.sender, amount_collateral)
```
```diff
@internal
def _redeem_collateral(
token_collateral_address: address,
amount_collateral: uint256,
_from: address,
_to: address,
):
- self.user_to_token_address_to_amount_deposited[_from][
- token_collateral_address
- ] -= amount_collateral
- log CollateralRedeemed(token_collateral_address, amount_collateral, _from, _to)
success: bool = extcall IERC20(token_collateral_address).transfer(
_to, amount_collateral
)
assert success, "DSCEngine_TransferFailed"
+ self.user_to_token_address_to_amount_deposited[_from][
+ token_collateral_address
+ ] -= amount_collateral
+ log CollateralRedeemed(token_collateral_address, amount_collateral, _from, _to)
```
Alternatively, implement reentrancy guards using Vyper's decorator system or a storage-based guard pattern.
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 16 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!