xanoutas/solidity-security-findings huggingface dataset:
registerAsset misuse can permanently disable TransmuterBuffer and break the system
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

TransmuterBuffer's refreshStrategies() is the only way to actualize _yieldTokens array. The function requires registeredUnderlyings array to match current Alchemist's _supportedUnderlyingTokens. In the same time registeredUnderlyings can be only increased via registerAsset(): there is no way to reduce, remove or reconstruct the array.

This way if registerAsset() was mistakenly called extra time or alchemist was switched with setAlchemist to a new one with less supported assets, then the strategy refresh becomes impossible and the TransmuterBuffer be blocked as it cannot be properly used without synchronization with Alchemist.

The redeployment of the contract doesn't provide an easy fix as it holds the accounting data that needs to be recreated (flowAvailable, currentExchanged mappings).

## Proof of Concept

refreshStrategies require registeredUnderlyings to be equal to Alchemist's supportedUnderlyingTokens:

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

```solidity
        if (registeredUnderlyings.length != supportedUnderlyingTokens.length) {
            revert IllegalState();
        }
```

If registeredUnderlyings has length more than Alchemist's _supportedUnderlyingTokens it doesn't look to be fixable and prohibits the future use of the contract, i.e. breaks the system.

## Recommended Mitigation Steps

The issue is that there is no way to unregister the asset, so consider introducing a function to remove the underlying or simply delete the array so it can be reconstructed with a sequence of registerAsset calls.
If `totalShares` for a token falls to zero while there is `pendingCredit` the contract will become stuck
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/AlchemistV2.sol#L1290-L1300
https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/AlchemistV2.sol#L1268
https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/AlchemistV2.sol#L1532
https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/AlchemistV2.sol#L899
https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/AlchemistV2.sol#L1625


# Vulnerability details

## Impact

It is possible for the contract to become stuck and unable to perform any actions if the `totalShares` of a yield token fall to zero while there is some `pendingCredit` still to be paid.

It will then be impossible to call deposit or withdraw functions, mints, burns, repay, liquidate, donate or harvest due to division by zero reverts in:

- `_distributeCredit()`
- `_distributeUnlockedCredit()`
- `_calculateUnrealizedDebt()`
- `_convertSharesToYieldTokens()`
- `donate()`

Furthermore, any `pendingCredit` amount of tokens are still in the contract will become permanently stuck.

## Proof of Concept

This case may arise under the follow steps
a) `deposit()` is called by a user then time passes to earn some yield
b) `harvest()` is called by the keeper which calls `_distributeCredit()` and increases `pendingCredit`
c) `withdraw()` is called by the user to withdraw all funds

Since there is `pendingCredit` the following will have a non-zero balance for `unlockedCredit` however `yieldTokenParams.totalShares` is zero and thus we get a division by zero which reverts the entire transaction.

```solidity
    function _distributeUnlockedCredit(address yieldToken) internal {
        YieldTokenParams storage yieldTokenParams = _yieldTokens[yieldToken];


        uint256 unlockedCredit = _calculateUnlockedCredit(yieldToken);
        if (unlockedCredit == 0) {
            return;
        }


        yieldTokenParams.accruedWeight     += unlockedCredit * FIXED_POINT_SCALAR / yieldTokenParams.totalShares;
        yieldTokenParams.distributedCredit += unlockedCredit;
    }
```

Each of the other listed functions will reach the same issue by attempting to divide some numerator by the `totalShares` which is zero.

## Recommended Mitigation Steps

Consider preventing `totalShares` from over becoming zero once it is set. That is enforce a user to leave at least 1 unit if they are the last user to withdraw.

Another option is to transfer the first 1000 shares to a "burn" account (e.g. 0x000...01), when the first user deposits.

Alternatively, when the last user withdraws, transfer all pending credit to this user and set the required variables to zero to replicate the state before any users have deposited.
YearnTokenAdapter's wrap can become stuck as it uses one step approval for an arbitrary underlying
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

Some tokens do not allow for approval of positive amount when allowance is positive already (to handle approval race condition, most known example is USDT).

This can cause the function to stuck whenever a combination of such a token and leftover approval be met. The latter can be possible if, for example, yearn vault is becoming full on a particular wrap() call and accepts only a part of amount, not utilizing the approval fully.

Then each next safeApprove will revert and wrap becomes permanently unavailable. Setting the severity to medium as depositing (wrapping) is core functionality for the contract and its availability is affected.

## Proof of Concept

wrap use one step approve:

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

```solidity
    function wrap(uint256 amount, address recipient) external override returns (uint256) {
        TokenUtils.safeTransferFrom(underlyingToken, msg.sender, address(this), amount);
        TokenUtils.safeApprove(underlyingToken, token, amount);
```

Some ERC20 forbid the approval of positive amount when the allowance is positive:

https://github.com/d-xo/weird-erc20#approval-race-protections

For example, USDT is supported by Yearn and can be the underlying asset:

https://yearn.finance/#/vault/0x7Da96a3891Add058AdA2E826306D812C638D87a7

## Recommended Mitigation Steps

As the most general approach consider approving zero before doing so for the amount:

```solidity
    function wrap(uint256 amount, address recipient) external override returns (uint256) {
        TokenUtils.safeTransferFrom(underlyingToken, msg.sender, address(this), amount);
+      TokenUtils.safeApprove(underlyingToken, token, 0);
        TokenUtils.safeApprove(underlyingToken, token, amount);
```
Lido adapter incorrectly calculates the price of the underlying token
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/adapters/lido/WstETHAdapterV1.sol#L82-L84


# Vulnerability details

## Impact

The Lido adapter incorrectly calculates the price of WETH in terms of WstETH.

The function returns the price of WstETH in terms of stETH. The underlying token which we desire is WETH.
Since stETH does not have the same value as WETH the output price incorrect.

The impact is severe as all the balance calculations require the price of the yield token converted to underlying. The incorrect price may over or understate the harvestable amount which is a core calculation in the protocol.

## Proof of Concept

The function `IWstETH(token).getStETHByWstETH()` only converts WstETH to stETH. Thus, `price()` returns the value of WstETH in terms of stETH.

```solidity
    function price() external view returns (uint256) {
        return IWstETH(token).getStETHByWstETH(10**SafeERC20.expectDecimals(token));
    }
```

## Recommended Mitigation Steps

Add extra steps to `price()` to approximate the rate for converting stETH to ETH. This can be done using the same curve pool that is used to convert stETH to ETH in `unwrap()`.
`lowerHasMinted()` can be used by minters to bypass `mintCeiling`
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

It is possible for any whitelisted used to reduce `totalMinted` for themselves. This value is used in `mint()` to prevent a malicious minter from minting an infinite number of tokens.

By allowing a minter to reduce their own `totalMinted` they are able to perform infinite minting.

## Proof of Concept

The attack by a malicious minter is to call `mint()` then `lowerHasMinted()` in a loop to mint as many tokens as desired.

The minter first calls `mint()` which will update their `totalMinted` to `amount` and mint `amount` to an arbitrary receiver address. They minter may set `amount = mintCeiling[msg.sender]` to maximise the amount they may mint each loop/ 

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

    totalMinted[msg.sender] = total;
```

The malicious minter may then call `lowerHasMinted` to reduce `totalMinted[msg.sender] = 0` for themselves.

```solidity
  function lowerHasMinted(uint256 amount) external onlyWhitelisted {
    totalMinted[msg.sender] = totalMinted[msg.sender] - amount;
  }
```

The malicious minter may now call `mint()` again for `mintCeiling[msg.sender]` to mint more tokens for themselves. They then repeat calls to `lowerHasMinted()` and `mint()` until they have minted the desired number of tokens.

## Recommended Mitigation Steps

There are two mitigations for this, the first is  to remove the function `lowerHasMinted()`. The other option is to make `lowerHasMinted()` an `adminOnly` function preventing minters from adjusting their owner amount.
EthAssetManager and ThreePoolAssetManager don't control Meta tokens decimals
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/ThreePoolAssetManager.sol#L896-L905
https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/EthAssetManager.sol#L566-L573


# Vulnerability details

Both contracts treat meta assets as if they have fixed decimals of 18. Minting logic breaks when it's not the case. However, meta tokens decimals aren't controlled.

If actual meta assets have any other decimals, minting slippage control logic of both contracts will break up as `total` is calculated as a plain sum of token amounts.

In the higher token decimals case `minTotalAmount` will be magnitudes higher than actual amount Curve can provide and minting becomes unavailable.

In the lower token decimals case `minTotalAmount` will lack value and slippage control will be rendered void, which opens up a possibility of a fund loss from the excess slippage.

Setting severity to medium as the contract can be used with various meta tokens (`_metaPoolAssetCache`  can be filled with any assets) and, whenever decimals differ from 18 `add_liquidity` uses, its logic be broken: the inability to mint violates the contract purpose, the lack of slippage control can lead to fund losses.

I.e. this is system breaking impact conditional on a low probability assumption of different meta token decimals.

## Proof of Concept

Meta tokens decimals are de facto hard coded into the contract as plain amounts are used (L. 905):

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/ThreePoolAssetManager.sol#L896-L905

```solidity
    function _mintMetaPoolTokens(
        uint256[NUM_META_COINS] calldata amounts
    ) internal returns (uint256 minted) {
        IERC20[NUM_META_COINS] memory tokens = _metaPoolAssetCache;

        uint256 total = 0;
        for (uint256 i = 0; i < NUM_META_COINS; i++) {
            if (amounts[i] == 0) continue;

            total += amounts[i];
```

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/ThreePoolAssetManager.sol#L915-L919

```solidity
        uint256 expectedOutput    = total * CURVE_PRECISION / metaPool.get_virtual_price();
        uint256 minimumMintAmount = expectedOutput * metaPoolSlippage / SLIPPAGE_PRECISION;

        // Add the liquidity to the pool.
        minted = metaPool.add_liquidity(amounts, minimumMintAmount);
```

The same plain sum approach is used in EthAssetManager._mintMetaPoolTokens:

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/EthAssetManager.sol#L566-L573

```solidity
        uint256 total = 0;
        for (uint256 i = 0; i < NUM_META_COINS; i++) {
            // Skip over approving WETH since we are directly swapping ETH.
            if (i == uint256(MetaPoolAsset.ETH)) continue;

            if (amounts[i] == 0
YearnTokenAdapter allows a maximum loss of 100% when withdrawing
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-full/adapters/yearn/YearnTokenAdapter.sol#L13
https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-full/adapters/yearn/YearnTokenAdapter.sol#L43


# Vulnerability details

## Impact
YearnTokenAdapter allows slippage of 100% when withdrawing from the vault which will cause a loss of funds.

Here's the documentation straight from the vault contract: https://github.com/yearn/yearn-vaults/blob/main/contracts/Vault.vy#L1025-L1073
It allows the user to specify the `maxLoss` as the last parameter. It determines how many shares can be burned to fulfill the withdrawal. Currently, the contract uses 10.000 which is 100%. Meaning there is no slippage check at all. This is bound to cause a loss of funds.

I'd suggest letting the user determine the slippage check themselves instead of hardcoding it.

## Proof of Concept
Current `maxLoss` value: https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-full/adapters/yearn/YearnTokenAdapter.sol#L13

call to Yearn vault's `withdraw()` function: https://github.com/code-423n4/2022-05-alchemix/blob/main/contracts-full/adapters/yearn/YearnTokenAdapter.sol#L43

## Tools Used
none

## Recommended Mitigation Steps
Allow the user to specify the slippage value themselves:

```sol
    function unwrap(uint256 amount, address recipient, uint maxLoss) external override returns (uint256) {
        TokenUtils.safeTransferFrom(token, msg.sender, address(this), amount);

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

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

        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;
    }
```

If you don't want to change the ITokenAdapter interface you can also leave the value blank. The vault will then use the default value (`0.01%`)
No Storage Gap for Upgradeable Contract Might Lead to Storage Slot Collision
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/AlchemicTokenV2Base.sol#L20
https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/CrossChainCanonicalBase.sol#L12
https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/TransmuterV2.sol#L26
https://github.com/code-423n4/2022-05-alchemix/blob/de65c34c7b6e4e94662bf508e214dcbf327984f4/contracts-full/CrossChainCanonicalAlchemicTokenV2.sol#L7


# Vulnerability details

## Impact

For upgradeable contracts, there must be storage gap to "allow developers to freely add new state variables in the future without compromising the storage compatibility with existing deployments" (quote OpenZeppelin). Otherwise it may be very difficult to write new implementation code. Without storage gap, the variable in child contract might be overwritten by the upgraded base contract if new variables are added to the base contract. This could have unintended and very serious consequences to the child contracts, potentially causing loss of user fund or cause the contract to malfunction completely.

Refer to the bottom part of this article: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable


## Proof of Concept

Several contracts are intended to be upgradeable contracts in the code base, including 
- AlchemicTokenV2Base
- CrossChainCanonicalBase
- CrossChainCanonicalAlchemicTokenV2
- TransmuterV2

However, none of these contracts contain storage gap. The storage gap is essential for upgradeable contract because "It allows us to freely add new state variables in the future without compromising the storage compatibility with existing deployments". Refer to the bottom part of this article:

https://docs.openzeppelin.com/contracts/3.x/upgradeable

As an example, both the `AlchemicTokenV2Base` and the `CrossChainCanonicalBase` are intended to act as the base contracts in the project. If the contract inheriting the base contract contains additional variable, then the base contract cannot be upgraded to include any additional variable, because it would overwrite the variable declared in its child contract. This greatly limits contract upgradeability. 


## Tools Used

Manual review

## Recommended Mitigation Steps

Recommend adding appropriate storage gap at the end of upgradeable contracts such as the below. Please reference OpenZeppelin upgradeable contract templates.

```solidity
uint256[50] private __gap;
```
using tx.origin does not guarantee EOA (3)
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
This condition does not guarantee that msg.sender is EOA.
The best is to use !isContract(msg.sender).
Having tx.origin == msg.sender is possible to bypass from another smartcontract which means attacker could have access to whitelist-protected functions.

## Proof of Concept
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/07b1b472c0ac3e50963c8d52cd2dac6e7e05260c/contracts/utils/Address.sol#L36

## Tools Used

## Recommended Mitigation Steps
using tx.origin does not guarantee EOA (2)
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
This condition does not guarantee that msg.sender is EOA.
The best is to use isContract(msg.sender).
Having tx.origin == msg.sender is possible to bypass from another smartcontract which means attacker could have access to whitelist-protected functions.

## Proof of Concept
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/07b1b472c0ac3e50963c8d52cd2dac6e7e05260c/contracts/utils/Address.sol#L36

## Tools Used

## Recommended Mitigation Steps
using tx.origin does not guarantee EOA (1)
Audit: 2022-05-alchemix-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
This condition does not guarantee that msg.sender is EOA.
The best is to use !isContract(msg.sender).
Having tx.origin == msg.sender is possible to bypass from another smartcontract which means attacker could have access to whitelist-protected functions.

## Proof of Concept
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/07b1b472c0ac3e50963c8d52cd2dac6e7e05260c/contracts/utils/Address.sol#L36

## Tools Used

## Recommended Mitigation Steps
A malicious actor can let market sponsors send sponsorship to unintended markets
Audit: 2021-06-realitycards-findings
Severity: HIGH

# Handle

shw


# Vulnerability details

## Impact

The function `sponsor(address _sponsorAddress, uint256 _amount)` in `RCMarket` is permissionless, which allows a malicious actor to sponsor any market in the name of some sponsor as long as enough tokens are approved. The malicious actor could then add sponsorship to markets where he will likely become the winner to gain more winning awards.

## Proof of Concept

1. Suppose there are two opening markets, MarketA and MarketB. A sponsor, Alice, wants to sponsor MarketA with 100 amounts of DAI. So, after approving the treasury to send her tokens, she calls `sponsor(100)` on MarketA.
2. A malicious actor, Bob, listens to the mempool and notices Alice's transaction. He then front-runs the transaction and calls `sponsor(Alice, 100)` on MarketB.
3. Bob's transaction is executed, and the treasury transfers 100 DAI from Alice to MarketB, which is not Alice's intention.

Besides front-running, Bob can also check the past transactions to find anyone who approved the treasury before. If the current allowance is greater than zero, he can call `sponsor` directly with the victim's address as the parameter to sponsor any market he wants.

Referenced code:
[RCMarket.sol#L817-L822](https://github.com/code-423n4/2021-06-realitycards/blob/main/contracts/RCMarket.sol#L817-L822)

## Recommended Mitigation Steps

Should check if the caller of the function `sponsor(address _sponsorAddress, uint256 _amount)` is the factory.
Return values of ERC20 `transfer` and `transferFrom` are unchecked
Audit: 2021-06-realitycards-findings
Severity: HIGH

# Handle

shw


# Vulnerability details

## Impact

In the contract `RCTreasury`, the return values of ERC20 `transfer` and `transferFrom` are not checked, which could be `false` if the transferred token is not ERC20-compliant. In that case, the transfer fails without being noticed by the calling contract.

## Proof of Concept

Referenced code:
[RCTreasury.sol#L350](https://github.com/code-423n4/2021-06-realitycards/blob/main/contracts/RCTreasury.sol#L350)
[RCTreasury.sol#L298](https://github.com/code-423n4/2021-06-realitycards/blob/main/contracts/RCTreasury.sol#L298)
[RCTreasury.sol#L373](https://github.com/code-423n4/2021-06-realitycards/blob/main/contracts/RCTreasury.sol#L373)
[RCTreasury.sol#L478](https://github.com/code-423n4/2021-06-realitycards/blob/main/contracts/RCTreasury.sol#L478)

## Recommended Mitigation Steps

Use the `SafeERC20` library [implementation](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/utils/SafeERC20.sol) from Openzeppelin and call `safeTransfer` or `safeTransferFrom` when transferring ERC20 tokens.