xanoutas/solidity-security-findings huggingface dataset:
amount requires to be updated to contract balance increase (7)
Audit: 2022-05-alchemix-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/71abbe683dfd5c0686b7e594fb4f78a14b668d8b/contracts-full/adapters/fuse/FuseTokenAdapterV1.sol#L66-L86


# Vulnerability details

## Impact
Every time transferFrom or transfer function in ERC20 standard is called there is a possibility that underlying smart contract did not transfer the exact amount entered.
It is required to find out contract balance increase/decrease after the transfer.
This pattern also prevents from re-entrancy attack vector.

## Proof of Concept

## Tools Used

## Recommended Mitigation Steps
Recommended code:
```solidity
function wrap(
    uint256 amount,
    address recipient
) external onlyAlchemist returns (uint256) {
    uint256 balanceBefore = IERC20(underlyingToken).balanceOf(address(this)); // remembering asset balance before the transfer
    SafeERC20.safeTransferFrom(underlyingToken, msg.sender, address(this), amount);
    amount = IERC20(underlyingToken).balanceOf(address(this)) - balanceBefore; // updating actual amount to the contract balance increase

    // no re-entrancy attack vector below here
    
    SafeERC20.safeApprove(underlyingToken, token, amount);

    uint256 startingBalance = IERC20(token).balanceOf(address(this));

    uint256 error;
    if ((error = ICERC20(token).mint(amount)) != NO_ERROR) {
        revert FuseError(error);
    }

    uint256 endingBalance = IERC20(token).balanceOf(address(this));
    uint256 mintedAmount = endingBalance - startingBalance;

    SafeERC20.safeTransfer(token, recipient, mintedAmount);

    return mintedAmount;
}
```
amount requires to be updated to contract balance increase (6)
Audit: 2022-05-alchemix-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/71abbe683dfd5c0686b7e594fb4f78a14b668d8b/contracts-full/adapters/yearn/YearnTokenAdapter.sol#L38-L57


# Vulnerability details

## Impact
Every time transferFrom or transfer function in ERC20 standard is called there is a possibility that underlying smart contract did not transfer the exact amount entered.
It is required to find out contract balance increase/decrease after the transfer.
This pattern also prevents from re-entrancy attack vector.

## Proof of Concept

## Tools Used

## Recommended Mitigation Steps
Recommended code:
```solidity
function unwrap(uint256 amount, address recipient) external override returns (uint256) {
    uint256 balanceBefore = TokenUtils.safeBalanceOf(token, address(this)); // remembering asset balance before the transfer
    TokenUtils.safeTransferFrom(token, msg.sender, address(this), amount);
    amount = TokenUtils.safeBalanceOf(token, address(this)) - balanceBefore; // updating actual amount to the contract balance increase

    // no re-entrancy attack vector below here

    balanceBefore = TokenUtils.safeBalanceOf(token, address(this));

    uint256 amountWithdrawn = IYearnVaultV2(token).withdraw(amount, recipient, MAXIMUM_SLIPPAGE);

    uint256 balanceAfter = TokenUtils.safeBalanceOf(token, address(this));

    // If the Yearn vault did not burn all of the shares then revert. This is critical in mathematical operations
    // performed by the system because the system always expects that all of the tokens were unwrapped. In Yearn,
    // this sometimes does not happen in cases where strategies cannot withdraw all of the requested tokens (an
    // example strategy where this can occur is with Compound and AAVE where funds may not be accessible because
    // they were lent out).
    if (balanceBefore - balanceAfter != amount) {
        revert IllegalState();
    }

    return amountWithdrawn;
}
```
amount requires to be updated to contract balance increase (5)
Audit: 2022-05-alchemix-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/71abbe683dfd5c0686b7e594fb4f78a14b668d8b/contracts-full/adapters/yearn/YearnTokenAdapter.sol#L30-L35


# Vulnerability details

## Impact
Every time transferFrom or transfer function in ERC20 standard is called there is a possibility that underlying smart contract did not transfer the exact amount entered.
It is required to find out contract balance increase/decrease after the transfer.
This pattern also prevents from re-entrancy attack vector.

## Proof of Concept

## Tools Used

## Recommended Mitigation Steps
Recommended code:
```solidity
function wrap(uint256 amount, address recipient) external override returns (uint256) {

    uint256 balanceBefore = TokenUtils.safeBalanceOf(underlyingToken, address(this)); // remembering asset balance before the transfer
    TokenUtils.safeTransferFrom(underlyingToken, msg.sender, address(this), amount);
    amount = TokenUtils.safeBalanceOf(underlyingToken, address(this)) - balanceBefore; // updating actual amount to the contract balance increase

    // no re-entrancy attack vector below here
    
    TokenUtils.safeApprove(underlyingToken, token, amount);

    return IYearnVaultV2(token).deposit(amount, recipient);
}
```
amount requires to be updated to contract balance increase (4)
Audit: 2022-05-alchemix-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/71abbe683dfd5c0686b7e594fb4f78a14b668d8b/contracts-full/TransmuterV2.sol#L207-L218


# Vulnerability details

## Impact
Every time transferFrom or transfer function in ERC20 standard is called there is a possibility that underlying smart contract did not transfer the exact amount entered.
It is required to find out contract balance increase/decrease after the transfer.
This pattern also prevents from re-entrancy attack vector.

## Proof of Concept

## Tools Used

## Recommended Mitigation Steps
Recommended code:
```solidity
function deposit(uint256 amount, address owner) external override {
    _onlyWhitelisted();
    _updateAccount(
        UpdateAccountParams({
        owner: owner,
        unexchangedDelta: SafeCast.toInt256(amount),
        exchangedDelta: 0
        })
    );    

    uint256 balanceBefore = TokenUtils.safeBalanceOf(syntheticToken, address(this)); // remembering asset balance before the transfer
    TokenUtils.safeTransferFrom(syntheticToken, msg.sender, address(this), amount);
    amount = TokenUtils.safeBalanceOf(syntheticToken, address(this)) - balanceBefore; // updating actual amount to the contract balance increase

    // no re-entrancy attack vector below here

    emit Deposit(msg.sender, owner, amount);
}
```

Note: nonReentrant can be safely removed here.
amount requires to be updated to contract balance increase (3)
Audit: 2022-05-alchemix-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/71abbe683dfd5c0686b7e594fb4f78a14b668d8b/contracts-full/AlchemistV2.sol#L743-L800


# Vulnerability details

## Impact
Every time transferFrom or transfer function in ERC20 standard is called there is a possibility that underlying smart contract did not transfer the exact amount entered.
It is required to find out contract balance increase/decrease after the transfer.
This pattern also prevents from re-entrancy attack vector.
Also consider moving all code using 'actualAmount' below safeTransferFrom.

## Proof of Concept

## Tools Used

## Recommended Mitigation Steps
Recommended code:
```solidity
function repay(address underlyingToken, uint256 amount, address recipient) external override returns (uint256) {
    _onlyWhitelisted();

    _checkArgument(amount > 0);
    _checkArgument(recipient != address(0));

    _checkSupportedUnderlyingToken(underlyingToken);
    _checkUnderlyingTokenEnabled(underlyingToken);

    // Distribute unlocked credit to depositors.
    _distributeUnlockedCreditDeposited(recipient);

    // Update the recipient's account and decrease the amount of debt incurred.
    _poke(recipient);

    // Check that the debt is greater than zero.
    //
    // It is possible that the amount of debt which is repayable is equal to or less than zero after realizing the
    // credit that was earned since the last update. We do not want to perform a noop so we need to check that the
    // amount of debt to repay is greater than zero.
    int256 debt;
    _checkState((debt = _accounts[recipient].debt) > 0);

    // Determine the maximum amount of underlying tokens that can be repaid.
    //
    // It is implied that this value is greater than zero because `debt` is greater than zero so a noop is not possible
    // beyond this point. Casting the debt to an unsigned integer is also safe because `debt` is greater than zero.
    uint256 maximumAmount = _normalizeDebtTokensToUnderlying(underlyingToken, uint256(debt));

    // Limit the number of underlying tokens to repay up to the maximum allowed.
    uint256 actualAmount = amount > maximumAmount ? maximumAmount : amount;

    // Transfer the repaid tokens to the transmuter.
    uint256 balanceBefore = TokenUtils.safeBalanceOf(underlyingToken, transmuter); // remembering asset balance before the transfer    
    TokenUtils.safeTransferFrom(underlyingToken, msg.sender, transmuter, actualAmount);
    actualAmount = TokenUtils.safeBalanceOf(underlyingToken, transmuter) - balanceBefore; // updating actual amount to the contract balance increase

    // no re-entrancy attack vector below here
    // can safely move everything using actualAmount below

    Limiters.LinearGrowthLimiter storage limiter = _repayLimiters[underlyingToken];

    // Check to make sure that the underlying token repay limit has not been breached.
    uint256 currentRepayLimit = limiter.get();
    if (actualAmount > currentRepayLimit) {
        revert Rep
amount requires to be updated to contract balance increase (2)
Audit: 2022-05-alchemix-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/71abbe683dfd5c0686b7e594fb4f78a14b668d8b/contracts-full/AlchemistV2.sol#L1309-L1326


# Vulnerability details

## Impact
Every time transferFrom or transfer function in ERC20 standard is called there is a possibility that underlying smart contract did not transfer the exact amount entered.
It is required to find out contract balance increase/decrease after the transfer.
This pattern also prevents from re-entrancy attack vector.

## Proof of Concept

## Tools Used

## Recommended Mitigation Steps
Recommended code:
```solidity
function _wrap(
    address yieldToken,
    uint256 amount,
    uint256 minimumAmountOut
) internal returns (uint256) {
    YieldTokenParams storage yieldTokenParams = _yieldTokens[yieldToken];

    ITokenAdapter adapter = ITokenAdapter(yieldTokenParams.adapter);
    address underlyingToken = yieldTokenParams.underlyingToken;

    uint256 balanceBefore = TokenUtils.safeBalanceOf(underlyingToken, address(this)); // remembering asset balance before the transfer
    TokenUtils.safeTransferFrom(underlyingToken, msg.sender, address(this), amount);
    amount = TokenUtils.safeBalanceOf(underlyingToken, address(this)) - balanceBefore; // updating actual amount to the contract balance increase

    // no re-entrancy attack vector below here

    uint256 wrappedShares = adapter.wrap(amount, address(this));
    if (wrappedShares < minimumAmountOut) {
        revert SlippageExceeded(wrappedShares, minimumAmountOut);
    }

    return wrappedShares;
}
```
amount requires to be updated to contract balance increase (1)
Audit: 2022-05-alchemix-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/71abbe683dfd5c0686b7e594fb4f78a14b668d8b/contracts-full/AlchemistV2.sol#L553-L569


# Vulnerability details

## Impact
Every time transferFrom or transfer function in ERC20 standard is called there is a possibility that underlying smart contract did not transfer the exact amount entered.
It is required to find out contract balance increase/decrease after the transfer.
This pattern also prevents from re-entrancy attack vector.

## Proof of Concept

## Tools Used

## Recommended Mitigation Steps
Recommended code:
```solidity
function deposit(
    address yieldToken,
    uint256 amount,
    address recipient
) external override returns (uint256) {
    _onlyWhitelisted();
    _checkArgument(recipient != address(0));
    _checkSupportedYieldToken(yieldToken);

    // Transfer tokens from the message sender now that the internal storage updates have been committed.
    uint256 balanceBefore = TokenUtils.safeBalanceOf(yieldToken, address(this)); // remembering asset balance before the transfer
    TokenUtils.safeTransferFrom(yieldToken, msg.sender, address(this), amount);
    amount = TokenUtils.safeBalanceOf(yieldToken, address(this)) - balanceBefore; // updating actual amount to the contract balance increase
    
    // no re-entrancy attack vector below here

    // Deposit the yield tokens to the recipient.
    uint256 shares = _deposit(yieldToken, amount, recipient);

    return shares;
}
```

Note: lock can be safely removed here.
TransmuterBuffer.sol calls depositUnderlying with no slippage bounds 
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/TransmuterBuffer.sol#L498-L505


# Vulnerability details

## Impact
Loss of funds in TransmuterBuffer

## Proof of Concept
If the buffer is called during and unfavorable time then a large portion of deposited funds may be lost due to slippage because deposit is called with 0 as the minimum out allowing any level of slippage

## Tools Used

## Recommended Mitigation Steps
Implement a slippage calculation similar to _alchemistWithdraw to protect against it
[gALCX.sol] Attacker can make the contract unusable when totalSupply is 0
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L73-L76


# Vulnerability details

## Impact
An attacker can make the contract unusable when totalSupply is 0. Specifically,  ``bumpExchangeRate`` function does not work correctly which results in making ``stake``, ``unstake`` and ``migrateSource`` functions that do not work as expected.

## Proof of Concept
Here are steps on how the ``gALCX`` contract can be unusable.

1. ``gALCX`` contract is deployed

2. The attacker sends the ``ALCX`` token to the deployed ``gALCX`` contract directly instead of using ``stake`` function so that the following ``balance`` variable has value.

https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L73-L75

```
uint balance = alcx.balanceOf(address(this));

if (balance > 0) {
```

3. Since the ``ALCX`` token is given to the ``gALCX`` contract directly, ``totalSupply == 0`` and ``alcx.balanceOf(address(this)) > 0`` becomes true.

https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-hardhat/gALCX.sol#L76

```
exchangeRate += (balance * exchangeRatePrecision) / totalSupply;
```

4. Non attackers try to call ``stake`` function, but ``bumpExchangeRate`` function fails because of ``(balance * exchangeRatePrecision) / totalSupply`` when totalSupply is 0.

5. Owner cannot call ``migrateSource`` function since ``bumpExchangeRate`` will be in the same situation mentioned in the step4 above

## Tools Used
Static code analysis

## Recommended Mitigation Steps
Add handling when ``totalSupply`` is 0 but ``alcx.balanceOf(address(this))`` is more than 0.
Alchemist can mint `AlTokens` above their assigned ceiling by calling `lowerHasMinted()`
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/AlchemicTokenV2Base.sol#L111-L124
https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/AlchemicTokenV2Base.sol#L189-L191


# Vulnerability details

## Impact
An alchemist / user can mint more than their alloted amount of AlTokens by calling `lowerHasMinted()` before they reach their minting cap.

## Proof of Concept
Function `mint()` in `AlchemicTokenV2Base.sol` 
```solidity
  function mint(address recipient, uint256 amount) external onlyWhitelisted {
    if (paused[msg.sender]) {
      revert IllegalState();
    }

    uint256 total = amount + totalMinted[msg.sender];
    if (total > mintCeiling[msg.sender]) {
      revert IllegalState();
    }

    totalMinted[msg.sender] = total;

    _mint(recipient, amount);
  }
```
Note the require conditional check that `total > mintCeiling[msg.sender]`.

In the same contract, there is the function `lowerHasMinted()` with the same permission level as mint and is thus callable by the same user as well.
```solidity
  function lowerHasMinted(uint256 amount) external onlyWhitelisted {
    totalMinted[msg.sender] = totalMinted[msg.sender] - amount;
  }
```

It is clear that a user can accumulate an infinite (within supply) amount of AlTokens by calling `lowerHasMinted()` before any action that would make them exceed their minting cap.

## Tools Used
Manual review, VScode

## Recommended Mitigation Steps
Change the permissioning on `lowerHasMinted()` to be restricted to a higher permissioned role like `onlySentinel()` , or deprecate this function as I could not find any uses of it throughout the codebase or in tests.
[WP-H3] Lack of liquidation makes it possible for alToken to be undercollateralized
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/AlchemistV2.sol#L803-L826


# Vulnerability details

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/AlchemistV2.sol#L803-L826

```solidity
function liquidate(
    address yieldToken,
    uint256 shares,
    uint256 minimumAmountOut
) external override lock returns (uint256) {
    _onlyWhitelisted();

    _checkArgument(shares > 0);

    YieldTokenParams storage yieldTokenParams = _yieldTokens[yieldToken];
    address underlyingToken = yieldTokenParams.underlyingToken;

    _checkSupportedYieldToken(yieldToken);
    _checkYieldTokenEnabled(yieldToken);
    _checkUnderlyingTokenEnabled(underlyingToken);
    _checkLoss(yieldToken);

    // Calculate the unrealized debt.
    //
    // It is possible that the number of debt which is repayable is equal to or less than zero after realizing the
    // credit that was earned since the last update. We do not want to perform a noop so we need to check that the
    // amount of debt to repay is greater than zero.
    int256 unrealizedDebt;
    _checkState((unrealizedDebt = _calculateUnrealizedDebt(msg.sender)) > 0);
    ...
```

In the current design/implementation, `liquidate()` can only be called by the user for themselves, which means, even if the market price of the collateral changes and for some users, when their collateral is close to being worth less than their debt, there will be no way for others to impound the collateral and reduce the risk of bad debt to the system.

When that happens, the alToken will be at risk to become undercollateralized. And all the rational users with good and valuable collateral assets will rush to get back their good collateral, and the ones with bad collateral assets will choose not to repay.

This will further dilute the avg value of the collateral, and put the whole system at a higher risk of becoming undercollateralized.

### PoC

Given:

- total collateral:
    - DAI: 300M (trading at $1)
    - USDT: 100M (trading at $1)
- outstanding debt: 300M alUSD, evenly distributed among all the collateral assets
- alUSD is trading at $1
- minimumCollateralization: 4/3

#### When the price of USDT slowly drops from $1 to $0.7:

All the users with DAI as collateral assets will repay and withdraw DAI.

As a result:

- the new total collateral:
    - USDT: 100M (trading at $0.7)
- outstanding debt: 75M alUSD

That's 75M alUSD backed by $70M worth of assets.

As of now, the alUSD will is an undercollateralized stablecoin.

### Recommendation

First, the `totalValue` of the collateral should be based on the market price rather than fixed at 1:1. See also the Recommendation section of [WP-H2].

Secondly, consider adding a new permissionless method for liquidation, which allows anyone to impound the collateral by repaying the debt for the user when the collateralization rate is lower than the liquidat
[WP-H2] Always use a 1:1 ratio for all the underlyingTokens when calculating the collateral value is flawed and can cause systemic failure when one of the underlyingTokens is depegged
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/AlchemistV2.sol#L1679-L1682


# Vulnerability details

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/AlchemistV2.sol#L1679-L1682

```solidity
function _normalizeUnderlyingTokensToDebt(address underlyingToken, uint256 amount) internal view returns (uint256) {
    return amount * _underlyingTokens[underlyingToken].conversionFactor;
}
```

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/AlchemistV2.sol#L1457-L1471

```solidity
function _totalValue(address owner) internal view returns (uint256) {
    uint256 totalValue = 0;

    Sets.AddressSet storage depositedTokens = _accounts[owner].depositedTokens;
    for (uint256 i = 0; i < depositedTokens.values.length; i++) {
        address yieldToken             = depositedTokens.values[i];
        address underlyingToken        = _yieldTokens[yieldToken].underlyingToken;
        uint256 shares                 = _accounts[owner].balances[yieldToken];
        uint256 amountUnderlyingTokens = _convertSharesToUnderlyingTokens(yieldToken, shares);

        totalValue += _normalizeUnderlyingTokensToDebt(underlyingToken, amountUnderlyingTokens);
    }

    return totalValue;
}
```

When an `underlyingToken` is used as collateral, the amount in terms of the `underlyingToken` (adjusted for decimals) will always be taken in a 1:1 ratio/price into `_totalValue`.

We believe this design is flawed and can cause systemic failure when one of the underlyingTokens is severely depegged.

Because when the underlyingToken is depegged, and the alToken minted is worthing more than the collateral underlyingToken in the market. The arbitrageur can simply mint and dump alToken as much as they can and choose to abandon the collateral.

### PoC

Given:

- alUSD is trading at $1
- minimumCollateralization: 4/3

#### When USDT is significantly depegged, and trading at `$0.7`:

An arbitrageur can:

1. Market buy `100M USDT` with `70M USDC`;
2. Deposit `100M USDT` as collateral and mint `75M alUSD` (100*0.75);
3. Market sell `75M alUSD` to `75M USDC`, profit for `5M USDC`.

### Recommendation

Consider using an oracle to query the market price of the underlyTokens, and calculate the `_totalValue` based on the market price.