xanoutas/solidity-security-findings huggingface dataset:
IRREVERSIBLE SHUTDOWN FUNCTION
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The abstract contract VaultImplementation is inherited by Vault.sol and PublicVault.sol. Its `shutdown()` can be called by the strategist to make `_loadVISlot().isShutdown = true`. This will make the modifier `whenNotPaused()` revert, rendering `commitToLien()` and `buyoutLien()` unable to execute forever because no where in the contract can be found a function to undo the shutdown.

## Proof of Concept
The second if block of the following modifier is dependent on `_loadVISlot().isShutdown`:

[VaultImplementation.sol#L131-L140](https://github.com/code-423n4/2023-01-astaria/blob/main/src/VaultImplementation.sol#L131-L140)

```
  modifier whenNotPaused() {
    if (ROUTER().paused()) {
      revert InvalidRequest(InvalidRequestReason.PAUSED);
    }

    if (_loadVISlot().isShutdown) {
      revert InvalidRequest(InvalidRequestReason.SHUTDOWN);
    }
    _;
  }
```
The above modifier is going to revert with the following function invoked:

[VaultImplementation.sol#L146-L150](https://github.com/code-423n4/2023-01-astaria/blob/main/src/VaultImplementation.sol#L146-L150)

```
  function shutdown() external {
    require(msg.sender == owner()); //owner is "strategist"
    _loadVISlot().isShutdown = true;
    emit VaultShutdown();
  }
```
Consequently, the following two functions whose visibility includes `whenNotPaused` will be non-callable:

[VaultImplementation.sol#L287-L306](https://github.com/code-423n4/2023-01-astaria/blob/main/src/VaultImplementation.sol#L287-L306)

```
  function commitToLien(
    IAstariaRouter.Commitment calldata params,
    address receiver
  )
    external
    whenNotPaused
    returns (uint256 lienId, ILienToken.Stack[] memory stack, uint256 payout)
  { ... }
```
[VaultImplementation.sol#L313-L330](https://github.com/code-423n4/2023-01-astaria/blob/main/src/VaultImplementation.sol#L313-L330)

```
  function buyoutLien(
    ILienToken.Stack[] calldata stack,
    uint8 position,
    IAstariaRouter.Commitment calldata incomingTerms
  )
    external
    whenNotPaused
    returns (ILienToken.Stack[] memory, ILienToken.Stack memory)
  { ... }
```
## Recommended Mitigation Steps
It is recommended implementing `unShutdown()` in the contract with opposite logic to `shutdown()` so that the strategist gets the option to reverse `whenNotPaused`.
CIE Violation Might Lead To Re-entrancy
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/main/src/CollateralToken.sol#L553-L585


# Vulnerability details

## Impact
CIE Violation in the onERC721Received function Might Lead To Re-entrancy.

## Proof of Concept
 In the function https://github.com/code-423n4/2023-01-astaria/blob/main/src/CollateralToken.sol#L553-L585 we perform the transfer here https://github.com/code-423n4/2023-01-astaria/blob/main/src/CollateralToken.sol#L578 but the function call may revert here https://github.com/code-423n4/2023-01-astaria/blob/main/src/CollateralToken.sol#L584 , so the transfer should be made in the end.

Moreover , if we can re-enter( Somewhere in the future the gas prices lets us do so , also there is no reentrancyGuard) , it may lead to re-entrancy as the attacker can reenter perform the transfer again then revert.
## Tools Used

Manual analysis

## Recommended Mitigation Steps

Follow the CIE pattern
ERC20 approve fail for some tokens
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

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


# 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
```
ClearingHouse.sol
148: ERC20(paymentToken).safeApprove(
149:      address(ASTARIA_ROUTER.TRANSFER_PROXY()),
150:      payment - liquidatorPayment
151:    );
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Use approve(0) to set the allowance to zero immediately before existing approve() calls.
Insufficient support for fee-on-transfer tokens
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Fee on transfer tokens are ERC20 tokens which charge a small fee for every transfer between accounts. This means that the amount of tokens sent and the amount received are not the same.
Some ERC20 tokens implemented so a fee is taken when transferring them.

## Proof of Concept
```
AstariaRouter.sol:
520: ERC20(IAstariaVaultBase(commitments[0].lienRequest.strategy.vault).asset())
521:      .safeTransfer(msg.sender, totalBorrowed);
784: token.safeTransferFrom(

ClearingHouse.sol:
143: ERC20(paymentToken).safeTransfer(
161: ERC20(paymentToken).safeTransfer(
217: ERC721(tokenContract).safeTransferFrom(address(this), target, tokenId);

PublicVault.sol:
378: ERC20(asset()).safeTransfer(currentWithdrawProxy, withdrawBalance);

TransferProxy.sol:
34: ERC20(token).safeTransferFrom(from, to, amount);

Vault.sol:
66: ERC20(asset()).safeTransferFrom(msg.sender, address(this), amount);
72: ERC20(asset()).safeTransferFrom(address(this), msg.sender, amount);

WithdrawProxy.sol:
274: ERC20(asset()).safeTransfer(VAULT(), balance);
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider getting the received amount by calculating the difference of token balance before and after the transfer.
Incorrect implementation resulted in inability to make small deposits when share prices are high
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/AstariaXYZ/astaria-gpl/blob/4b49fe993d9b807fe68b3421ee7f2fe91267c9ef/src/ERC4626-Cloned.sol#L19-L36


# Vulnerability details



## Impact

In the current implementation, `PublicVault.sol#minDepositAmount()` is intended to prevent token deposits that are too small. However, in `ERC4626-Cloned.sol#deposit()`, the `minDepositAmount` is incorrectly used to compare with the number of shares obtained from this deposit to confirm compliance with the minimum deposit requirement, which does not match the return unit of `PublicVault.sol#minDepositAmount()`. 

Due to the features of `PublicVault`, as profits increase, share prices will continue to rise. Under high share prices, the current implementation will continue to increase the minimum deposit amount, preventing more and more small investors

## Proof of Concept

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/PublicVault.sol#L96-L108

```solidity=96
function minDepositAmount()
    public
    view
    virtual
    override(ERC4626Cloned)
    returns (uint256)
  {
    if (ERC20(asset()).decimals() == uint8(18)) {
      return 100 gwei;
    } else {
      return 10**(ERC20(asset()).decimals() - 1);
    }
  }
```

https://github.com/AstariaXYZ/astaria-gpl/blob/4b49fe993d9b807fe68b3421ee7f2fe91267c9ef/src/ERC4626-Cloned.sol#L19-L36

```solidity=19
function deposit(uint256 assets, address receiver)
    public
    virtual
    returns (uint256 shares)
  {
    // Check for rounding error since we round down in previewDeposit.
    require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");

    require(shares > minDepositAmount(), "VALUE_TOO_SMALL");
    // Need to transfer before minting or ERC777s could reenter.
    ERC20(asset()).safeTransferFrom(msg.sender, address(this), assets);

    _mint(receiver, shares);

    emit Deposit(msg.sender, receiver, assets, shares);

    afterDeposit(assets, shares);
  }
```

Given:

- `asset` = USDC
- `minDepositAmount` = 10**5 (0.1 USDC)
-  share price is now 10

1. Alice call `minDepositAmount()` got `100,000`

2. Alice call `deposit()` try deposit `200,000`, tx revert with `VALUE_TOO_SMALL` 


## Recommended Mitigation Steps

Consider Change to:

```solidity=19
function deposit(uint256 assets, address receiver)
    public
    virtual
    returns (uint256 shares)
  {
    // Check for rounding error since we round down in previewDeposit.
    require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");

    require(assets > minDepositAmount(), "VALUE_TOO_SMALL");
    // Need to transfer before minting or ERC777s could reenter.
    ERC20(asset()).safeTransferFrom(msg.sender, address(this), assets);

    _mint(receiver, shares);

    emit Deposit(msg.sender, receiver, assets, shares);

    afterDeposit(assets, shares);
  }
```
## Use safeMint instead of mint for ERC721 I
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

_safeMint() should be used if you are sending the minted token to a Contract that is capable to manage NFTs or not. This is to prevent tokens to be lost.

WithdrawProxy.sol:139:    _mint(receiver, shares);
CollateralToken.sol:588:      _mint(from_, collateralId);
PublicVault.sol:512:    _mint(msg.sender, unclaimed);
LienToken.sol:455:    _mint(params.receiver, newLienId);

## Recommendation

Use `safeMint` instead of `mint` to check received address support for ERC721 implementation.

[https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol#L262](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol#L262)
Improper design results in large minimum deposit requirement for high-value tokens, preventing most investors
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/PublicVault.sol#L96-L108


# Vulnerability details


## Impact
If the asset is WBTC, under the current design, the minDepositAmount will be 0.1 WBTC, valued more than 2000 US dollars, which is a excessively high minimum single investment amount for most investors, seriously affecting the efficiency of fundraising investment.

## Proof of Concept

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/PublicVault.sol#L96-L108

```solidity=96
function minDepositAmount()
    public
    view
    virtual
    override(ERC4626Cloned)
    returns (uint256)
  {
    if (ERC20(asset()).decimals() == uint8(18)) {
      return 100 gwei;
    } else {
      return 10**(ERC20(asset()).decimals() - 1);
    }
  }
```

WBTC decimals is `8`, `minDepositAmount()` for WBTC will be `10**7 wei` worth more than 2000 USD

## Recommended Mitigation Steps

consider add a storage `minDepositAmount` in `VIData`, and let owner set it
Unbound gas consumption loop
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
If Stack[] array is largerin size which will cause unbound gas consumption loop .Because each iteration it will call storage variables of  LienStorage storage s and made some calculation if caller run-out of gas then entire function will fail.

## Proof of Concept
https://github.com/code-423n4/2023-01-astaria/blob/main/src/LienToken.sol#L304

## Tools Used
Manual View

## Recommended Mitigation Steps
I suggest to limit the max number of loop iterations to prevent hitting block gas limit.

## Reference 
https://www.notion.so/tom-sol/Unbounded-Loop-e49a6bbe4860460aa5f0016f1480c048
Deletion on mapping containing a structure
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
A deletion in a structure containing a mapping will not delete the mapping. The remaining data may be used to compromise the contract. 

## Proof of Concept

```
CollateralToken.sol
537: delete s.idToUnderlying[collateralId];
544: delete s.collateralIdToAuction[collateralId];

LienToken.sol
243: delete s.lienMeta[oldLienId];
372 delete s.lienMeta[id].payee;
509: delete s.collateralStateHash[collateralId];
645: delete s.lienMeta[lienId];
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Use a lock mechanism instead of a deletion to disable structure containing a mapping.
Check tokenAddress when using solmate safeTransfer
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
- Using `safeTransferFrom` of solmate.
- There is no checking the token address is exist

[https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol#L9](https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol#L9)


## Proof of Concept
```solidity
  function _transferAndDepositAssetIfAble(
    RouterStorage storage s,
    address tokenContract,
    uint256 tokenId
  ) internal {
    ERC721 token = ERC721(tokenContract);
    if (token.ownerOf(tokenId) == msg.sender) {
      token.safeTransferFrom(
        msg.sender,
        address(s.COLLATERAL_TOKEN),
        tokenId,
        ""
      );
    }
  }
```
## Tools Used
vscode
## Recommended Mitigation Steps
Use Openzeppelin safeTransferFrom or check the token contract exists.
Lender withdraw token amount less then expected 
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/PublicVault.sol#L170
https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/PublicVault.sol#L174


# Vulnerability details

## Impact
Lender could withdraw an amount minor to what is expecting if withdrawble tokens available are low, and his shares are completely burnt.

The process of withdrawing consists on first signaling the withdraw in advance through the Astaria Router, then a WithdrawProxy is created (if not already created) and after 2 epoch lender can redeem shares.
With `ASTARIA_ROUTER.redeemFutureEpoch(IPublicVault vault,uint256 shares,address receiver,uint64 epoch)` the lender signal the withdraw.
At src/PublicVault.sol#L170 shares are converted to assets by `shares.mulDivDown(totalAssets(), supply);`
At src/PublicVault.sol#L174 shares account of lender is updated `es.balanceOf[owner] -= shares;` therefore if he is redeeming all shares his account in Vault goes to 0.
At src/PublicVault.sol#L184 WithdrawProxy is deployed if not existing
At src/PublicVault.sol#L194 share in WithdrawProxy are minted for lender

With `PublicVault(publicVault).transferWithdrawReserve();` anyone can transfer available asset() to WithdrawProxy active during same epoch.
At src/PublicVault.sol#L384 and 385 withdrawable assets are transferred 
`ERC20(asset()).safeTransfer(currentWithdrawProxy, withdrawBalance);`
`WithdrawProxy(currentWithdrawProxy).increaseWithdrawReserveReceived(withdrawBalance);`

With `WithdrawProxy(withdrawProxy).redeem(WithdrawProxy(withdrawProxy).maxRedeem(address(1)),address(1),address(1));` lender can redeem his shares 
At lib/gpl/src/ERC4626-Cloned.sol#L94 assets to withdraw are calculated from shares.
`return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply);`

That means that if in PublicVault there were not enough assets to cover all assets rapresented by lender shares, the lender will get less amount and shares are all burnt,  loosing funds.

## Proof of Concept
For instance let's see this simulation where:
Lender1 deposit 20eth
Lender2 deposit 30eth
Borrower borrows 40eth
Lender1 signal withdrawal
Lien expires and liquidation is required
Auction NOT completed
Lender1 redeems his shares, got back 10Eth but all his shares are burnt.

Try this POC using forge test --ffi --mt testLenderRequireWithdrawGetsBackHalfAmount -vv and following the console.log output

    function testLenderRequireWithdrawGetsBackHalfAmount() public {
    TestNFT nft = new TestNFT(2);
    address tokenContract = address(nft);
    uint256 tokenId0 = uint256(0);
    // create a PublicVault with a 14-day epoch
    address publicVault = _createPublicVault({
      strategist: strategistOne,
      delegate: strategistTwo,
      epochLength: 14 days
    });

    vm.label(address(publicVault),"PublicVault");

    // lend 50 ether to the PublicVault as address(1)
    _lendToVault(
      Lender({addr: ad
Users are unable to mint shares from a public vault using `AstariaRouter` contract when share price is bigger than one
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/AstariaXYZ/astaria-gpl/blob/main/src/ERC4626RouterBase.sol#L15-L25
https://github.com/AstariaXYZ/astaria-gpl/blob/main/src/ERC4626-Cloned.sol#L38-L52
https://github.com/AstariaXYZ/astaria-gpl/blob/main/src/ERC4626-Cloned.sol#L129-L133


# Vulnerability details

## Impact
For a public vault, calling the `AstariaRouter.mint` function calls the following `ERC4626RouterBase.mint` function and then the `ERC4626Cloned.mint` function below. After user actions like borrowing and repaying after some time, the public vault's share price can become bigger than 1 so `ERC4626Cloned.previewMint` function's execution of `shares.mulDivUp(totalAssets(), supply)` would return `assets` that is bigger than `shares`; then, calling the `ERC4626Cloned.mint` function would try to transfer this `assets` from `msg.sender` to the public vault. When the `AstariaRouter.mint` function is called, this `msg.sender` is the `AstariaRouter` contract. However, because the `AstariaRouter.mint` function only approves the public vault for transferring `shares` of the asset token on behalf of the router, the `ERC4626Cloned.mint` function's transfer of `assets` of the asset token would fail due to the insufficient asset token allowance. Hence, when the public vault's share price is bigger than 1, users are unable to mint shares from the public vault using the `AstariaRouter` contract and cannot utilize the slippage control associated with the `maxAmountIn` input.

https://github.com/AstariaXYZ/astaria-gpl/blob/main/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();
    }
  }
```

https://github.com/AstariaXYZ/astaria-gpl/blob/main/src/ERC4626-Cloned.sol#L38-L52
```solidity
  function mint(
    uint256 shares,
    address receiver
  ) public virtual returns (uint256 assets) {
    assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.
    require(assets > minDepositAmount(), "VALUE_TOO_SMALL");
    // Need to transfer before minting or ERC777s could reenter.
    ERC20(asset()).safeTransferFrom(msg.sender, address(this), assets);

    _mint(receiver, shares);

    emit Deposit(msg.sender, receiver, assets, shares);

    afterDeposit(assets, shares);
  }
```

https://github.com/AstariaXYZ/astaria-gpl/blob/main/src/ERC4626-Cloned.sol#L129-L133
```solidity
  function previewMint(uint256 shares) public view virtual returns (uint256) {
    uint256 supply = totalSupply(); // Saves an extra SLOAD if totalSupply is non-zero.

    return supply == 0 ? 10e18 : shares.mulDivUp(totalAssets(), supply);
  }
```

## Proof of Concept
Please add the following test in `src\test\AstariaTest.t.sol`. This test will pass to demonstra
minDepositAmount is compared against a wrong variable
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

https://github.com/AstariaXYZ/astaria-gpl/blob/4b49fe993d9b807fe68b3421ee7f2fe91267c9ef/src/ERC4626-Cloned.sol#L27
https://github.com/AstariaXYZ/astaria-gpl/blob/4b49fe993d9b807fe68b3421ee7f2fe91267c9ef/src/ERC4626-Cloned.sol#L43


# Vulnerability details

The following `require` statement In `deposit` of ERC4626-Cloned.sol

```solidity
require(shares > minDepositAmount(), "VALUE_TOO_SMALL");
```

is ensuring `shares` is greater than `minDepositAmount()`, even though it should be ensuring `assets > minDepositAmount()` (like how it’s done in the `mint` method).

## Impact

An unexpected amount of `assets` can be deposited; or a valid amount of `assets` cannot be deposited.

## Proof of Concept

See the attached links.

## Tools Used

Manual analysis

## Recommended Mitigation Steps

Fix the require statement with the right invariant.
Wrong amount approval may impair depositing to vaults via `AstariaRouter`
Audit: 2023-01-astaria-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
User may not be able to deposit funds to a vault if the number of shares to be minted is smaller than the amount of assets to be deposited.
## Proof of Concept
[AstariaRouter](https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/AstariaRouter.sol#L53) inherits from `ERC4626Router` and [ERC4626RouterBase](https://github.com/AstariaXYZ/astaria-gpl/blob/4b49fe993d9b807fe68b3421ee7f2fe91267c9ef/src/ERC4626RouterBase.sol#L11), which implements functions that allow users to interact with vaults via `AstariaRouter`. The [mint](https://github.com/AstariaXYZ/astaria-gpl/blob/4b49fe993d9b807fe68b3421ee7f2fe91267c9ef/src/ERC4626RouterBase.sol#L15) function allows users to mint a user-specified number of shares by depositing an amount of assets that's calculated by the vault, [depending on total shares and the amount of shares to be minted](https://github.com/AstariaXYZ/astaria-gpl/blob/4b49fe993d9b807fe68b3421ee7f2fe91267c9ef/src/ERC4626-Cloned.sol#L42).

The amount of tokens the `mint` function of `ERC4626RouterBase` approves to the underlying vault is `shares`, however it should be the amount of assets to be deposited. In situations when the number of shares is smaller than the number of assets to be deposited, the function will revert.

### Exploit Scenario
1. Liquidity providers deposited 100 ETH to a public vault. The vault also has 1 ETH of pending interests to be paid by borrowers. Thus, [totalAssets](https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/PublicVault.sol#L479-L493) of the vault is 101 ETH.
1. A user tries to mint 1e18 of shares via the `mint` function of `ERC4626RouterBase`. The [previewMint](https://github.com/AstariaXYZ/astaria-gpl/blob/4b49fe993d9b807fe68b3421ee7f2fe91267c9ef/src/ERC4626-Cloned.sol#L129-L133) function of the vault will convert 1e18 shares to 1.01e18 ETH.
1. Since `ERC4626RouterBase` approves 1e18 ETH to the vault and not 1.01e18 ETH, the call to `mint` will revert.
## Tools Used
Manual review
## Recommended Mitigation Steps
Consider this change:
```diff
diff --git a/src/ERC4626RouterBase.sol b/src/ERC4626RouterBase.sol
index a20651e..f2a6b7e 100644
--- a/src/ERC4626RouterBase.sol
+++ b/src/ERC4626RouterBase.sol
@@ -18,7 +18,8 @@ abstract contract ERC4626RouterBase is IERC4626RouterBase, Multicall {
     uint256 shares,
     uint256 maxAmountIn
   ) public payable virtual override returns (uint256 amountIn) {
-    ERC20(vault.asset()).safeApprove(address(vault), shares);
+    uint256 amount = vault.previewMint(shares);
+    ERC20(vault.asset()).safeApprove(address(vault), amount);
     if ((amountIn = vault.mint(shares, to)) > maxAmountIn) {
       revert MaxAmountError();
     }
```