xanoutas/solidity-security-findings huggingface dataset:
[WP-H1] Debt can be repaid with a depegged underlyingToken, which can be exploited by arbitrageurs and drives the market price of alToken to match the worst depegged underlyingToken
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#L743-L786

```solidity
function repay(address underlyingToken, uint256 amount, address recipient) external override lock returns (uint256) {
    ...
    uint256 credit = _normalizeUnderlyingTokensToDebt(underlyingToken, actualAmount);

    // Update the recipient's debt.
    _updateDebt(recipient, -SafeCast.toInt256(credit));
    ...
```

When repaying the debt with an `underlyingToken`, the amount in terms of the `underlyingToken` (adjusted for decimals) will always be taken in a 1:1 ratio/price for the subtrahend of the debt.

We believe this design is flawed and be exploited by arbitrageurs and eventually drives the market price of alToken to match the worst depegged underlyingToken.

Because if `alToken` is trading at a higher price against the depegged underlyingToken, the arbitrageur can always mint alToken and market sell for more depegged underlyingToken and repay the debt.

### PoC

Given:

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

#### When USDT is slightly depegged, and trading at `$0.9`:

An arbitrageur can:

1. Deposit `100M USDC` as collateral and mint `75M alUSD` (100*0.75);
2. Market buy `75M USDT` with `67.5M alUSD` (75*0.9) and repay the debt;
3. Withdraw the `100M USDC` collateral;
4. Dump the remaining `7.5M alUSD` (75-67.5) for profit.

This can be repeated until the market price of alUSD drops to `$0.9`, the same price as USDT.

### Recommendation

Consider updating the `repay()` function and change to market buy using the underlyingToken to alToken and then `burn()` the alToken to reduce debt.
DoS in wrap and unwrap
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/adapters/fuse/FuseTokenAdapterV1.sol#L76
https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/adapters/fuse/FuseTokenAdapterV1.sol#L98


# Vulnerability details

## Impact
the code is doing wrong check, so when things will work it will revert.


## Proof of Concept
In the function `wrap()` there is this lines:
```
   if ((error = ICERC20(token).mint(amount)) != NO_ERROR) {
            revert FuseError(error);
        }
```
but `mint` returns the amount that minted, so when `error = amount` the check will fail even though it worked good.

Same in `unwrap`:
```
if ((error = ICERC20(token).redeem(amount)) != NO_ERROR) {
            revert FuseError(error);
        }
```
the redeem returns the amount.


## Recommended Mitigation Steps

I recommend to change the lines like this:
    in wrap:
    ```
     if ((error = ICERC20(token).mint(amount)) != amount) {
            revert FuseError(error);
        }
    ```
    and in unwrap:
    ```
    if ((error = ICERC20(token).redeem(amount)) != amount) {
            revert FuseError(error);
        }
    ```
A well financed attacker could prevent any other users from minting synthetic tokens
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-full/AlchemistV2.sol#L676-L683
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-full/AlchemistV2.sol#L704
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-full/libraries/Limiters.sol#L84


# Vulnerability details

## Impact
In the `AlchemistV2` contract, users can deposit collateral to then borrow/mint the synthetic tokens offered by the protocol. The protocol also defines a minting limit that specifies how many synthetic tokens can be minted in a given time period. This exists to prevent unbounded minting of synthetic tokens.

Every time a user calls `mint`, the internal `_mint` method decreases the current mint limit. This works as intended. However, there is nothing stopping an attacker from immediately burning their synthetic tokens by calling `burn` and then calling `mint` again. This is possible because the debt position is updated during the `burn` phase, which lets the user then mint again against the same deposited collateral.

In most cases this probably wouldn't be a problem if the mint limit is sufficiently high. However, it is currently possible for a well financed attacker to grief the contract by repeatedly minting and burning synthetic tokens to keep the contract pegged at the mint limit. This will prevent any normal users from minting any synthetic tokens, and hence prevents the protocol from performing as it should.

## Proof of Concept
An attacker can repeatedly call `mint` followed by `burn` after depositing some collateral with `deposit`. If this is appropriately sized and timed, it can cause the `mint` call to fail for another user due to the check [here](https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-full/AlchemistV2.sol#L1068) that is called during `mint` [here](https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-full/AlchemistV2.sol#L1192).

## Tools Used
VSCode

## Recommended Mitigation Steps
There should be an additional method added to the `Limiters` library that can increment the mint limit. This method can then be called during a `burn` call in the `AlchemistV2` contract.

```
function increase(LinearGrowthLimiter storage self, uint256 amount) internal {
  uint256 value = self.get();
  self.lastValue = value + amount > self.maximum ? self.maximum : value + amount;
}
```
AutoleverageBase: Must approve 0 first
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/71abbe683dfd5c0686b7e594fb4f78a14b668d8b/contracts-full/AutoleverageBase.sol#L61-L63


# Vulnerability details

## Impact
Some tokens (like USDT) do not work when changing the allowance from an existing non-zero allowance value.They must first be approved by zero and then the actual allowance must be approved.
## Proof of Concept
https://github.com/code-423n4/2022-05-alchemix/blob/71abbe683dfd5c0686b7e594fb4f78a14b668d8b/contracts-full/AutoleverageBase.sol#L61-L63
https://github.com/code-423n4/2022-05-alchemix/blob/71abbe683dfd5c0686b7e594fb4f78a14b668d8b/contracts-full/AutoleverageBase.sol#L147-L147
https://github.com/code-423n4/2022-05-alchemix/blob/71abbe683dfd5c0686b7e594fb4f78a14b668d8b/contracts-full/AutoleverageBase.sol#L178-L179
## Tools Used
None
## Recommended Mitigation Steps
```
    function approve(address token, address spender) internal {
    +  IERC20(token).approve(spender, 0);
        IERC20(token).approve(spender, type(uint256).max);
    }
```
New gALCX token denomination can be depressed by the first depositor
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-hardhat/gALCX.sol#L93-L94
https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/gALCX.sol#L69-L76


# Vulnerability details

An attacker can become the first depositor for a recently created gALCX contract, providing a tiny amount of ALCX tokens by calling `stake(1)` (raw values here, `1` is `1 wei`, `1e18` is `1 ALCX`). Then the attacker can directly transfer, for example, `10^6 * 1e18 - 1` of ALCX to the gALCX contract and run bumpExchangeRate(), effectively setting the cost of `1` gALCX to be `10^6 * 1e18` of ALCX. The attacker will still own 100% of the gALCX's ALCX pool being the only depositor.

All subsequent depositors will have their ALCX token investments rounded to `10^6 * 1e18`, due to the lack of precision which initial tiny deposit caused, with the remainder divided between all current depositors, i.e. the subsequent depositors lose value to the attacker.

For example, if the second depositor brings in `1.9*10^6 * 1e18` of ALCX, only `1` of new vault to be issued as `1.9*10^6 * 1e18` divided by `10^6 * 1e18` will yield just `1`, which means that `2.9*10^6 * 1e18` total ALCX pool will be divided 50/50 between the second depositor and the attacker, as each will have `1 wei` of the total `2 wei` of vault tokens, i.e. the depositor lost and the attacker gained `0.45 * 10^6 * 1e18` of ALCX tokens.

As there are no penalties to exit with gALCX.unstake(), the attacker can remain staked for an arbitrary time, gathering the share of all new deposits' remainder amounts.

Placing severity to be medium as this is principal funds loss scenario for many users (most of depositors), easily executable, but only new gALCX contract instances are vulnerable.

## Proof of Concept

gAmount of gALCX to be minted is determined as a quotient of `amount` provided and `exchangeRate`:

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-hardhat/gALCX.sol#L93-L94

```solidity
        uint gAmount = amount * exchangeRatePrecision / exchangeRate;
        _mint(msg.sender, gAmount);
```

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/gALCX.sol#L15

```solidity
uint public constant exchangeRatePrecision = 1e18;
```

`exchangeRate` accumulates balance increments relative to total gALCX supply:

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/gALCX.sol#L69-L76

```solidity
    function bumpExchangeRate() public {
        // Claim from pool
        pools.claim(poolId);
        // Bump exchange rate
        uint balance = alcx.balanceOf(address(this));

        if (balance > 0) {
            exchangeRate += (balance * exchangeRatePrecision) / totalSupply;
```

When gALCX contract is new, the very first stake -> bumpExchangeRate()
AlchemicTokenV2Base minters can always surpass mint ceiling threshold
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

AlchemicTokenV2Base's lowerHasMinted() function has `onlyWhitelisted` access control. Any minter is whitelisted, it is required to be able to run mint(). Each minters' mint total amount is controlled by totalMinted cumulative counter, which can be reduced by running lowerHasMinted().

This way any minter can arbitrary lower mint limit for herself, so mint limit can be surpassed, it actually controls only a single mint amount. Even small mintCeiling allows for unlimited mint.

Setting severity to high as this is a direct violation of system logic propagated to any contract that inherits from AlchemicTokenV2Base (it's CrossChainCanonicalAlchemicTokenV2 in the scope).

## Proof of Concept

Any minter is whitelisted as it's required in order to use mint():

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/AlchemicTokenV2Base.sol#L103-L119

```solidity
  /// @notice Mints tokens to `a recipient.`
  ///
  /// @notice This function reverts if `msg.sender` is not whitelisted.
  /// @notice This function reverts if `msg.sender` is paused.
  /// @notice This function reverts if `msg.sender` has exceeded their mintable ceiling.
  ///
  /// @param recipient The address to mint the tokens to.
  /// @param amount    The amount of tokens to mint.
  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();
    }
```

Any whitelisted, and this way any minter, can run lowerHasMinted function, arbitrary lowering totalMinted:

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

```solidity
  /// @notice Lowers the number of tokens which the `msg.sender` has minted.
  ///
  /// @notice This reverts if the `msg.sender` is not whitelisted.
  ///
  /// @param amount The amount to lower the minted amount by.
  function lowerHasMinted(uint256 amount) external onlyWhitelisted {
    totalMinted[msg.sender] = totalMinted[msg.sender] - amount;
  }
```

This way totalMinted can be kept to be zero by any minter, and mintCeiling can only control one mint() amount instead of the total. Even small mintCeiling allows for unlimited mint by repeating `mint -> lowerHasMinted` call sequence.

I.e. mint amount limitation can be surpassed by any minter, the limit is effectively rendered void.

## Recommended Mitigation Steps

Consider making `lowerHasMinted` `onlySentinel` or `onlyAdmin`.
TransmuterBuffer's _alchemistWithdraw use hard coded slippage that can lead to user losses
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-hardhat/TransmuterBuffer.sol#L511-L524


# Vulnerability details

exchange() -> _exchange() -> _alchemistWithdraw() is user funds utilizing call sequence and the slippage hard coded to 1% there can cause a range of issues.

For example, if there is not enough shares, the number of shares to withdraw will be unconditionally reduced to the number of the shares available. This can pass under 1% slippage and user will give away up to 1% without giving a consent to such a fee, which is big enough to notice.

On the other hand, in a similar situation when there is not enough shares available a user might knowingly want to execute with even bigger fee, but hard coded slippage will not be met and the withdraw be unavailable and funds frozen.

Setting the severity to medium as the end impact is either modest user fund loss or exchange functionality unavailability.

## Proof of Concept

_alchemistWithdraw uses hard coded 1% slippage threshold and rewrites wantShares to be availableShares once TransmuterBuffer's position isn't big enough:

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-hardhat/TransmuterBuffer.sol#L511-L524

```solidity
    function _alchemistWithdraw(address token, uint256 amountUnderlying) internal {
        uint8 decimals = TokenUtils.expectDecimals(token);
        uint256 pricePerShare = IAlchemistV2(alchemist).getUnderlyingTokensPerShare(token);
        uint256 wantShares = amountUnderlying * 10**decimals / pricePerShare;
        (uint256 availableShares, uint256 lastAccruedWeight) = IAlchemistV2(alchemist).positions(address(this), token);
        if (wantShares > availableShares) {
            wantShares = availableShares;
        }
        // Allow 1% slippage
        uint256 minimumAmountOut = amountUnderlying - amountUnderlying * 100 / 10000;
        if (wantShares > 0) {
            IAlchemistV2(alchemist).withdrawUnderlying(token, wantShares, address(this), minimumAmountOut);
        }
    }
```

Alchemist's _unwrap will revert withdrawUnderlying call once minimumAmountOut isn't met:

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

```solidity
        if (amountUnwrapped < minimumAmountOut) {
            revert SlippageExceeded(amountUnwrapped, minimumAmountOut);
        }
```

There are 2 use cases for the function:

exchange (onlyKeeper) -> _exchange -> _alchemistWithdraw,

setFlowRate (onlyAdmin) -> _exchange -> _alchemistWithdraw

exchange() is the most crucial as it should be able to fulfil various types of user funds exchange requests:

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

```solidity
    /// @notice Pull necessary funds from the Alchemist and exchange them.
Exchange Rate Race Condition
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-full/gALCX.sol#L69-L81


# Vulnerability details

## GAX-01M: Exchange Rate Race Condition

| File | Lines | Type |
| :- | :- | :- |
| gALCX.sol | [L69-L81](https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-full/gALCX.sol#L69-L81) | Improper State Assumption |

### Description

The `gALCX` contains a race condition whereby whenever the contract has no stakes (such as when the contract is first deployed) the `exchangeRate` can be artificially inflated via direct ALCX transfers. In detail, a user can make a small ALCX deposit, perform a direct transfer of ALCX to the contract, redeem the ALCX and repeat to artifically inflate the `exchangeRate` to a potentially significant degree affecting truncations.

### Impact

The `exchangeRate` can be trivially inflated rendering the contract usable only by users making large deposits (due to truncation) or entirely unusable if the rate is inflated to a significant degree surpassing a decent percentage of the total supply of the ALCX asset. We would like to note that since inflation is possible via the usage of flash-loans, the size of `exchangeRate` can be easily exarcebated to significant proportions.

### Solution (Recommended Mitigation Steps)

We advise the `bumpExchangeRate` function to contain an alternative execution path that checks if the `totalSupply` is zero and in such a case simply resets the `exchangeRate` to `1e18` to ensure that the contract behaves properly under all circumstances. Alternatively, if the `exchangeRate` is envisioned to continuously increase perpetually (a trait we advise against), the code should evaluate the delta between `balanceOf` invocations "sandwhiching" the `claim` operation similarly to how the various adapters of the protocol operate.

### PoC

Issue is deducible by inspecting the relevant lines referenced in the issue and making note of the `balanceOf` invocation that does not care about whether the funds came from the `claim` operation.

### Tools 

Manual inspection of the codebase.
ATB-01M: Inexplicable Capability of Self-Limit Reduction
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## ATB-01M: Inexplicable Capability of Self-Limit Reduction

| File | Lines | Type |
| :- | :- | :- |
|  AlchemicTokenV2Base.sol | [L189-L191](https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-full/AlchemicTokenV2Base.sol#L189-L191) | Access Control |

### Description

The `mint` function is meant to prevent arbitrary amount mints via the `totalMinted` variable being guaranteed to be less than `mintCeiling` as set by the administrator of the system. However, the `lowerHasMinted` function permits a whitelisted member to lower their own mints thereby effectively bypassing the `mintCeiling` check and rendering it redundant.

### Impact

The whitelisted parties can completely bypass the `mintCeiling` limits set by the administrator thereby rendering minting entirely up to the discretion of the whitelisted parties.

### Solution (Recommended Mitigation Steps)

We advise alternative access control to be imposed by `lowerHasMinted` so that the restriction structure for mints makes sense in the system.

### PoC

Issue is immediately deducible by inspecting the relevant lines referenced in the issue.

### Tools 

Manual inspection of the codebase.
TransmuterBuffer's setAlchemist will freeze deposited funds
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

Currently setAlchemist doesn't check whether there are any open positions left with the old Alchemist before switching to the new one.

As this require a number of checks the probability of operational mistake isn't low and it's prudent to introduce the main controls directly to the code to minimize it. In the case if the system go on with new Alchemist before realizing that there are some funds left in the old one, tedious and error prone manual recovery will be needed. There is also going to be a corresponding reputational damage.

Setting the severity to medium as while the function is admin only, the impact is up to massive user fund freeze, i.e. this is system breaking with external assumptions.

## Proof of Concept

Alchemist implementation change can happen while there are open deposits remaining with the current contract. As there looks to be no process to transfer them in the code, such TransmuterBuffer's funds will be frozen with old alchemist:

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-hardhat/TransmuterBuffer.sol#L230-L232

```solidity
    function setAlchemist(address _alchemist) external override onlyAdmin {
        sources[alchemist] = false;
        sources[_alchemist] = true;
```

## Recommended Mitigation Steps

Consider requiring that all exposure to the old Alchemist is closed, for example both `getAvailableFlow` and `getTotalCredit` is zero.

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

```solidity
    function setAlchemist(address _alchemist) external override onlyAdmin {
+		  require(getTotalCredit() == 0, "Credit exists with old Alchemist");
+       for (uint256 j = 0; j < registeredUnderlyings.length; j++) {
+           require(getTotalUnderlyingBuffered[registeredUnderlyings[j]] == 0, "Buffer exists with old Alchemist");
+       }
        sources[alchemist] = false;
```