xanoutas/solidity-security-findings huggingface dataset:
Wrong implementations in `ERC4626RouterBase` contract
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/AstariaXYZ/astaria-gpl/blob/4b49fe993d9b807fe68b3421ee7f2fe91267c9ef/src/ERC4626RouterBase.sol#L11


# Vulnerability details

The `ERC4626RouterBase` contract contains a set of functions that act as wrappers for a ERC4626 contract, providing a base periphery functionality around a ERC4626 vault.

There are a number of different flaws in the wrapped implementations of `mint`, `deposit`, `withdraw` and `redeem`.

## Impact

All four functions present in the contract are marked as payable (because of the `IERC4626RouterBase` interface definitions) but don't need to handle ETH payments, as these operate on the assumption that the underlying `asset` is an ERC20 token. This will cause loss of funds if a user sends ETH to any of the functions.

Note that these functions are exposed as payable functions in the `AstariaRouter` contract, as this contract inherits from `ERC4626Router`.

### `mint`

https://github.com/AstariaXYZ/astaria-gpl/blob/4b49fe993d9b807fe68b3421ee7f2fe91267c9ef/src/ERC4626RouterBase.sol#L15-L25

```solidity
function mint(
  IERC4626 vault,
  address to,
  uint256 shares,
  uint256 maxAmountIn
) public payable virtual override returns (uint256 amountIn) {
  ERC20(vault.asset()).safeApprove(address(vault), shares);
  if ((amountIn = vault.mint(shares, to)) > maxAmountIn) {
    revert MaxAmountError();
  }
}
```

The `mint` function uses `shares` as the amount to `ERC20.approve` but this value is wrong, as shares is the output of the vault. It should first call `previewMint` to get the amount of the underlying token needed to mint that number of shares. Compare that amount against the `maxAmountIn` and then call `ERC20.approve` with that value.

### `withdraw` and `redeem`

https://github.com/AstariaXYZ/astaria-gpl/blob/4b49fe993d9b807fe68b3421ee7f2fe91267c9ef/src/ERC4626RouterBase.sol#L41-L52

```solidity
function withdraw(
  IERC4626 vault,
  address to,
  uint256 amount,
  uint256 maxSharesOut
) public payable virtual override returns (uint256 sharesOut) {

  ERC20(address(vault)).safeApprove(address(vault), amount);
  if ((sharesOut = vault.withdraw(amount, to, msg.sender)) > maxSharesOut) {
    revert MaxSharesError();
  }
}
```

https://github.com/AstariaXYZ/astaria-gpl/blob/4b49fe993d9b807fe68b3421ee7f2fe91267c9ef/src/ERC4626RouterBase.sol#L55-L66

```solidity
function redeem(
  IERC4626 vault,
  address to,
  uint256 shares,
  uint256 minAmountOut
) public payable virtual override returns (uint256 amountOut) {

  ERC20(address(vault)).safeApprove(address(vault), shares);
  if ((amountOut = vault.redeem(shares, to, msg.sender)) < minAmountOut) {
    revert MinAmountError();
  }
}
```

Both function calls to `ERC20.approve` are not valid, since `ERC4626.withdraw` will burn the shares and no transfers of shares are needed. 

### Recommendations

- In all cases, since functions are marked as payable, verify the value sent is 0 `require(msg.value == 0)` to prevent accidental loss of funds.

- For `
A user can use the same proof for a commitment more than 1 time
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/AstariaRouter.sol#L490
https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/AstariaRouter.sol#L761
https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/VaultImplementation.sol#L287
https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/VaultImplementation.sol#L379
https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/VaultImplementation.sol#L229
https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/VaultImplementation.sol#L178
https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/AstariaRouter.sol#L434
https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/strategies/UNI_V3Validator.sol#L80


# Vulnerability details

## Impact
A user can use the same commitment signature and merkleData more than 1 time to obtain another loan.
## Proof of Concept

A user needs to make some procedures to take a loan against an NFT.
Normally the user calls commitToLiens() in AstariaRouter.sol providing IAstariaRouter.Commitment[] as input.
https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/AstariaRouter.sol#L490

The function will transfer the NFT to the CollateralToken contract to get a collateral id for the user (see CollateralToken.onERC721Received() ). 

After that, the function in the router will make an internal call _executeCommitment() for each commitment.
_executeCommitment() checks the collateral id and the commitment.lienRequest.strategy.vault and then calls commitToLien in 
commitment.lienRequest.strategy.vault with the commitment and the router address as the receiver (ie: address (this)).

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/AstariaRouter.sol#L761

_executeCommitment() in AstariaRouter.sol

```solidity
  function _executeCommitment(
    RouterStorage storage s,
    IAstariaRouter.Commitment memory c
  )
    internal
    returns (
      uint256,
      ILienToken.Stack[] memory stack,
      uint256 payout
    )
  {
    uint256 collateralId = c.tokenContract.computeId(c.tokenId);

    if (msg.sender != s.COLLATERAL_TOKEN.ownerOf(collateralId)) {
      revert InvalidSenderForCollateral(msg.sender, collateralId);
    }
    if (!s.vaults[c.lienRequest.strategy.vault]) {
      revert InvalidVault(c.lienRequest.strategy.vault);
    }
    //router must be approved for the collateral to take a loan,
    return
      IVaultImplementation(c.lienRequest.strategy.vault).commitToLien( // HERE
        c,
        address(this)
      );
  }
```

commitToLien() function in VaultImplementation:

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c2
_buyoutLien() does not properly validate the liquidationInitialAsk
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/LienToken.sol#L148-L163


# Vulnerability details

## Impact
Illegal liquidationInitialAsk, resulting in insufficient bids to cover the debt
## Proof of Concept

_buyoutLien() will validate against liquidationInitialAsk, but incorrectly uses the old stack for validation

```solidity
  function _buyoutLien(
    LienStorage storage s,
    ILienToken.LienActionBuyout calldata params
  ) internal returns (Stack[] memory newStack, Stack memory newLien) {

....

    uint256 potentialDebt = 0;
    for (uint256 i = params.encumber.stack.length; i > 0; ) {
      uint256 j = i - 1;
      // should not be able to purchase lien if any lien in the stack is expired (and will be liquidated)
      if (block.timestamp >= params.encumber.stack[j].point.end) {
        revert InvalidState(InvalidStates.EXPIRED_LIEN);
      }

      potentialDebt += _getOwed(
        params.encumber.stack[j],
        params.encumber.stack[j].point.end
      );

      if (
        potentialDebt >
        params.encumber.stack[j].lien.details.liquidationInitialAsk   //1.****@audit use old stack
      ) {
        revert InvalidState(InvalidStates.INITIAL_ASK_EXCEEDED);
      }

      unchecked {
        --i;
      }
    }  
....
    newStack = _replaceStackAtPositionWithNewLien(
      s,
      params.encumber.stack,
      params.position,
      newLien,
      params.encumber.stack[params.position].point.lienId    //2.****@audit replace newStack
    );    
```

## Tools Used

## Recommended Mitigation Steps

Replace then verify, using the newStack[] for verification
PublicVault Contract Allows Minting Tokens to a Null Address
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/main/src/PublicVault.sol#L189


# Vulnerability details

## Impact

The PublicVault contract allows the minting of tokens to a null address. When the *redeemFutureEpoch* function is called with a null address as the receiver, the function will still proceed with the minting of the underlying asset, but the tokens will not have a valid destination address. As a result, these tokens may become locked. This could potentially result in a funds loss for the users of the contract.

## Proof of Concept

The vulnerable line is https://github.com/code-423n4/2023-01-astaria/blob/main/src/PublicVault.sol#L189

## Recommended Mitigation Steps

A require statement can be added to the redeemFutureEpoch function to check that the passed receiver address is not null before proceeding with the minting. An example would be:

```
require( receiver != address(0), "The receiver cannot be a null address")
```
settleAuction() Check for status errors
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/CollateralToken.sol#L526-L534


# Vulnerability details

## Impact
ClearingHouse.safeTransferFrom() to execute successfully even if there is no bid

## Proof of Concept
settleAuction is called at the end of the auction and will check if the status is legal

```solidity

  function settleAuction(uint256 collateralId) public {
    if (
      s.collateralIdToAuction[collateralId] == bytes32(0) &&
      ERC721(s.idToUnderlying[collateralId].tokenContract).ownerOf(
        s.idToUnderlying[collateralId].tokenId
      ) !=
      s.clearingHouse[collateralId]
    ) {
      revert InvalidCollateralState(InvalidCollateralStates.NO_AUCTION);
    }
```

This check seems to be miswritten，The normal logic would be

```solidity
s.collateralIdToAuction[collateralId] == bytes32(0) || ERC721(s.idToUnderlying[collateralId].tokenContract).ownerOf(
        s.idToUnderlying[collateralId].tokenId
      ) == s.clearingHouse[collateralId]
```

This causes ClearingHouse.safeTransferFrom() to execute successfully even if there is no bid

## Tools Used

## Recommended Mitigation Steps

```solidity

  function settleAuction(uint256 collateralId) public {
    if (
-     s.collateralIdToAuction[collateralId] == bytes32(0) &&
-    ERC721(s.idToUnderlying[collateralId].tokenContract).ownerOf(
-        s.idToUnderlying[collateralId].tokenId
-      ) !=
-      s.clearingHouse[collateralId]
+      s.collateralIdToAuction[collateralId] == bytes32(0) || 
+       ERC721(s.idToUnderlying[collateralId].tokenContract).ownerOf(s.idToUnderlying[collateralId].tokenId
+      ) == 
+      s.clearingHouse[collateralId]
    ) {
      revert InvalidCollateralState(InvalidCollateralStates.NO_AUCTION);
    }
```
Solmate safetransfer and safetransferfrom does not check the codesize of the token address, which may lead to fund loss
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/AstariaRouter.sol#L519


# Vulnerability details

## Impact

Possible miscalculation and loss of funds.

## Proof of Concept

In AstariaRouter.commitToLiens(), the safetransfer and safetransferfrom doesn’t check the existence of code at the token address. This is a known issue while using solmate’s libraries.Hence this may lead to miscalculation of funds and may lead to loss of funds , because if safetransfer() and safetransferfrom() are called on a token address that doesn’t have contract in it, it will always return success, bypassing the return value check. Due to this protocol will think that funds has been transferred and successful , and records will be accordingly calculated, but in reality funds were never transferred. So this will lead to miscalculation and possibly loss of funds.

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/AstariaRouter.sol#L519

## Tools Used

Manual Review

## Recommended Mitigation Steps

Use openzeppelin’s safeERC20 or implement a code existence check.
Front-run Initializer
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/AstariaRouter.sol#L83


# Vulnerability details

## Impact

In function:

    function initialize(
    Authority _AUTHORITY,
    ICollateralToken _COLLATERAL_TOKEN,
    ILienToken _LIEN_TOKEN,
    ITransferProxy _TRANSFER_PROXY,
    address _VAULT_IMPL,
    address _SOLO_IMPL,
    address _WITHDRAW_IMPL,
    address _BEACON_PROXY_IMPL,
    address _CLEARING_HOUSE_IMPL
     ) external initializer {
    //@audit front-run initializer
    __initAuth(msg.sender, address(_AUTHORITY));
    RouterStorage storage s = _loadRouterSlot();

Any user can front-run the initialize function which would grant authority to their address

## Proof of Concept
Steps to reproduce:

Monitor the Ethereum blockchain to check and call the initialize function once the contract is deployed.
Add malicious parameters.
The contract would be in a DOS state

## Tools Used
manual

## Recommended Mitigation Steps

Add a modifier that only owner can call the initialize function and initialize the owner variable in the constructor

         require(msg.sender == owner);
makePayment() may increase the user's debt
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/LienToken.sol#L596


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

## Proof of Concept
makePayment() will be called when the user needs to repay the money, in two cases.
```
1. If the repayment amount >= (borrowed amount + interest), then the loan is paid off and the corresponding LienToken is deleted
2. If the repayment amount < (borrowed amount + interest), then the borrowed amount = the remaining amount (borrowed amount + interest - repayment amount)
```

Case 2, if the repayment amount is not enough when not taking into account a situation, the repayment amount is not even enough interest, according to case 2 of the principal becomes larger, resulting in the subsequent interest will become larger

Example:
alice borrowing amount:1000 rate:10%/year borrowing 2 years
After 1 year borrowing amount = 1000, interest = 100

At this point the user repays: 50, which will result in alice's borrowing amount becoming 1050

When the two years are up, alice will have to repay a total of: 1050 + 1050*10% = 1155
Together with the 50 already repaid, alice actually repay 1155 + 50 = 1205

Normal if 2 years at most should be to pay back 1000 + 1000 * 10% * 2 = 1200

As above, resulting in an extra repayment : 5
So case 2 is not reasonable

## Tools Used

## Recommended Mitigation Steps

If the repayment amount is less than the current interest rate, directly revert
LienToken.transferFrom There is a possibility of malicious attack
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/LienToken.sol#L366-L368


# Vulnerability details

## Impact
Corrupt multiple key properties of public vault, causing vault not to function properly

## Proof of Concept

When LienToken.makePayment()/buyoutLien()/payDebtViaClearingHouse()
If it corresponds to PublicVault, it will make multiple changes to the vault, such as: yIntercept, slope, last, epochData, etc.

If LienToken corresponds to PublicVault, then ownerOf(lienId) = PublicVault address

When the LienToken is a private vault, it is possible to transfer the owner of the LienToken.

As the above seems, if the private vault is transferred to the PublicVault address will result in the wrong modification of the yIntercept, slope, last, epochData, etc.

So we restrict the to in transferFrom to not be a PublicVault address

```solidity

  function transferFrom(
    address from,
    address to,
    uint256 id
  ) public override(ERC721, IERC721) {

    if (_isPublicVault(s, to)) {  //***@audit when to == PublicVault address , will revert
      revert InvalidState(InvalidStates.PUBLIC_VAULT_RECIPIENT);  
    }  
```

However, such a restriction does not prevent an attacker from transferring PrivateVault's LienToken to PublicVault
Because the address is predictable when the vault contract is created, a malicious user can predict the vault address, front-run, and transfer PrivateVault's LienToken to the predicted PublicVault address before the public vault is created, thus bypassing this restriction

Assumptions:
1. alice creates PrivateVault, and creates multiple PrivateVault's LienToken
2. alice monitors bob's creation of the PublicVault transaction, i.e., AstariaRouter.newPublicVault(), and then predicts the address of the newly generated treasure chest
Note: newPublicVAult() although the use of create(), but still can predict the address

see：https://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed
```
The address for an Ethereum contract is deterministically computed from the address of its creator (sender) and how many transactions the creator has sent (nonce). The sender and nonce are RLP encoded and then hashed with Keccak-256.

```
3.front-run , and transfer LienToken to public vault predict address
4.bob's public vault created success and do some loan
5.alice do makePayment() to Corrupt bob's public vault



## Tools Used

## Recommended Mitigation Steps

The corresponding vault address is stored in s.lienMeta[id].orginOwner when the LienToken is created, this is not modified. Get the vault address from this variable, not from ownerOf(id).
INCORRECT FUNCTION CALLS
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/main/src/Vault.sol#L28-L36
https://github.com/code-423n4/2023-01-astaria/blob/main/src/WithdrawProxy.sol#L103-L111
https://github.com/code-423n4/2023-01-astaria/blob/main/src/PublicVault.sol#L76-L84


# Vulnerability details

## Impact
In Vault.sol, WithdrawProxy.sol and PublicVault.sol, `name()` is making an incorrect external call to return its output variable. Apparently, it is calling `ERC20(asset()).symbol())` instead of `ERC20(asset()).name())`. Devoid of an accurate name description, this could lead to confusion and perhaps trick users/developers into mistaking it for another asset. 

## Proof of Concept
The following instances show the wrong inclusion of the second variable in `string(abi.encodePacked()`: 

[Vault.sol#L28-L36](https://github.com/code-423n4/2023-01-astaria/blob/main/src/Vault.sol#L28-L36)

```
  function name()
    public
    view
    virtual
    override(VaultImplementation)
    returns (string memory)
  {
    return string(abi.encodePacked("AST-Vault-", ERC20(asset()).symbol()));
  }
```
[WithdrawProxy.sol#L103-L111](https://github.com/code-423n4/2023-01-astaria/blob/main/src/WithdrawProxy.sol#L103-L111)

```
  function name()
    public
    view
    override(IERC20Metadata, WithdrawVaultBase)
    returns (string memory)
  {
    return
      string(abi.encodePacked("AST-WithdrawVault-", ERC20(asset()).symbol()));
  }
```
[PublicVault.sol#L76-L84](https://github.com/code-423n4/2023-01-astaria/blob/main/src/PublicVault.sol#L76-L84)

```
  function name()
    public
    view
    virtual
    override(IERC20Metadata, VaultImplementation)
    returns (string memory)
  {
    return string(abi.encodePacked("AST-Vault-", ERC20(asset()).symbol()));
  }
```
## Recommended Mitigation Steps
It is recommended replacing `ERC20(asset()).symbol())` with `ERC20(asset()).name())` in the return statement to clear up the errors.
Comparison Of Different Types Might Lead to Inconsistent Behavior
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/main/src/PublicVault.sol#L375


# Vulnerability details

## Impact
Comparison Of Different Types Might Lead to Inconsistent Behavior

## Proof of Concept

The comparison here https://github.com/code-423n4/2023-01-astaria/blob/main/src/PublicVault.sol#L375 compares two different types , i.e. uint88 (withdrawReserve) and uint256 withdrawBalance. This type of mismatched might induce unwanted behavior (If a bug allowed old solidity somewhere in the code and uint88 will hold $309,485,009 which is achievable through flash loan , it may lead to overflow.unwanted behavior)

## Tools Used

Manual analysis

## Recommended Mitigation Steps

Convert the withdrawBalance to uint88
[M-02] Strategist has full control over Public Vault it can be risky for depositors
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/VaultImplementation.sol#L104-L117


# Vulnerability details

## Impact

The strategist has full control over all key functions. If the strategist is compromised or hacked he will be able to manipulate the vault. For example, increase the depositCap modifyDepositCap(uint256 newCap), add a depositor to the whitelist modifyAllowList(address depositor, `bool enabled)`, or disable the whitelist `disableAllowList()` and anyone can become a depositor or even shutdown vault. This will break the strategist's intended mechanics and can attract victims to the vault under his control and lead to the loss or freezing of their deposits.

## Proof of Concept

```solidity
file: VaultImplementation.sol

function modifyDepositCap(uint256 newCap) external {
    require(msg.sender == owner()); //owner is "strategist"
    ...
}

function modifyAllowList(address depositor, bool enabled) external virtual {
    require(msg.sender == owner()); //owner is "strategist"
		...
}

function disableAllowList() external virtual {
    require(msg.sender == owner()); //owner is "strategist"
		...
}

function enableAllowList() external virtual {
    require(msg.sender == owner()); //owner is "strategist"
    ...
}

function shutdown() external {
    require(msg.sender == owner()); //owner is "strategist"
    ...
}

function setDelegate(address delegate_) external {
    require(msg.sender == owner()); //owner is "strategist"
    ...
}
```

## Tools Used

Manual review

## Recommended Mitigation Steps

Consider using an role-based access control approach instead of a single admin role for public vault.
[M-01] Public Vault cannot be reactivated after Shutdown.
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/VaultImplementation.sol#L146-L150


# Vulnerability details

## Impact

If the strategist who created the vault is compromised/hacked or accidentally/intentionally shutdown the vault by call `shutdown()` the public vault cannot be resumed. There is no function that sets isShutdown = false. Unintentional shutdown of a large public vault may cause unexpected user behavior and lock liquidity provider funds for epoch length.

## Proof of Concept

```solidity
file: VaultImplementation.sol

modifier whenNotPaused() {
    ...
    if (_loadVISlot().isShutdown) {
      revert InvalidRequest(InvalidRequestReason.SHUTDOWN);
    }
    _;
}

function commitToLien(...) external whenNotPaused returns (...)
function buyoutLien(...) external whenNotPaused returns (...)

file: PublicVault.sol is VaultImplementation

function mint(uint256 shares, address receiver) public override(ERC4626Cloned) whenNotPaused returns (uint256)
function deposit(uint256 amount, address receiver) public override(ERC4626Cloned) whenNotPaused returns (uint256)

```

## Tools Used

Manual review

## Recommended Mitigation Steps

Add function `resumeVault()` to public vault and consider using an role-based access control approach to this function and to `shutdown()` function.