xanoutas/solidity-security-findings huggingface dataset:
ERC4626Cloned deposit and mint logic differ on first deposit
Audit: 2023-01-astaria-findings
Severity: HIGH

# Lines of code

https://github.com/AstariaXYZ/astaria-gpl/blob/4b49fe993d9b807fe68b3421ee7f2fe91267c9ef/src/ERC4626-Cloned.sol#L123-L127
https://github.com/AstariaXYZ/astaria-gpl/blob/4b49fe993d9b807fe68b3421ee7f2fe91267c9ef/src/ERC4626-Cloned.sol#L129-L133


# Vulnerability details

The `ERC4626Cloned` contract is an implementation of the ERC4626 used for vaults. The standard contains a `deposit` function to deposit a specific amount of the underlying asset, and a `mint` function that will calculate the amount needed of the underlying token to mint a specific number of shares.

This calculation is done in `previewDeposit` and `previewMint`:

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

```solidity
function previewDeposit(
  uint256 assets
) public view virtual returns (uint256) {
  return convertToShares(assets);
}

function convertToShares(
  uint256 assets
) public view virtual returns (uint256) {
  uint256 supply = totalSupply(); // Saves an extra SLOAD if totalSupply is non-zero.

  return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
}
```

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

In the case of the first deposit (i.e. when `supply == 0`), `previewDeposit` will return the same `assets` amount for the shares (this is the standard implementation), while `previewMint` will simply return `10e18`.

## Impact

It seems the intention was to mint a high initial number of shares on first deposit, so an attacker couldn't mint a low number of shares and manipulate the pool to frontrun an initial depositor.

However, the protocol has failed to replicate this logic in the `deposit` function, as both `deposit` and `mint` logic differ (see PoC).

An attacker can still use the `deposit` function to mint any number of shares.

## PoC

```solidity
contract MockERC20 is ERC20("Mock ERC20", "MERC20", 18) {
    function mint(address account, uint256 amount) external {
        _mint(account, amount);
    }
}

contract TestERC4626 is ERC4626Cloned {
    ERC20 _asset;

    constructor() {
        _asset = new MockERC20();
    }


    function asset() public override view returns (address assetTokenAddress) {
        return address(_asset);
    }

    function minDepositAmount() public override view returns (uint256) {
        return 0;
    }

    function totalAssets() public override view returns (uint256) {
        return _asset.balanceOf(address(this));
    }

    function symbol() external override view returns (string memory) {
        return "TEST4626";
    }
    function name() external override view returns (string memory) {
ERC4626Cloned.previewMint() causes malicious users to can  exclusive access to vault
Audit: 2023-01-astaria-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
 exclusive access to vault,Other users will not be able to mint

## Proof of Concept

In the previewMint method, if the first user mint, no matter how many shares the user passes in, only 10e18 assets are needed
```solidity
  function previewMint(uint256 shares) public view virtual returns (uint256) {
    uint256 supply = totalSupply(); // Saves an extra SLOAD if totalSupply is non-zero.
    //****@audit when supply ==0, ignore shares, Direct return 10e18 ***//
    return supply == 0 ? 10e18 : shares.mulDivUp(totalAssets(), supply);
  }
```

So that users can mint(shares=type(uint256).max), only need 10e18 asset 

Other users will not be able to mint, because totalSupply will overflow

## Tools Used

## Recommended Mitigation Steps

asset 1:1 return
Reentrancy in safeTransferFrom() in ClearingHouse.sol allows the attacker to withdraw the collateral, while still owning the borrowed assets.
Audit: 2023-01-astaria-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/ClearingHouse.sol#L114-L169


# Vulnerability details

# Impact Detailed description of the impact of this finding. 

Calling the function safeTransferFrom() and passing in a custom, attacker controlled payment token allows the malicious actor to perform reentrancy. https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/ClearingHouse.sol#L153-L158 the following function clears the attacker of all debt, which is costless to the attacker because the token is worthless. After the debt is cleared, the following check is performed: https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/ClearingHouse.sol#L160, this is where the attacker reenters. Attacker calls the releaseToAddress() in collateral token contract, and then he calls into a malicious erc721 contract, the malicious erc721 contract then calls into this function: https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/CollateralToken.sol#L553, and because the idToUnderlying is deleted, it can overwrite it and it won't revert and the nft is sent to the attacker, and he gets to keep it.

## Proof of Concept Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

 Create a malicious contract. In the contract, define a custom balanceOf() function which calls into CollateralToken's releaseToAddress() passing in the collateralId and calls the custom, attacker controlled erc721 contract. Because the balanceOf() of our malicious contract is called not once, but twice, make a variable that stores which time the function is being called like this pseudo code:

uint var;
erc721 erc721Contract; //this is our malicious erc721 contract which calls into onErc721Received() in collateralToken, which overwrites the underlying id
function balanceOf() public returns (uint){
if(var == 0){ 
var = var + 1;
return anyAmountToPassTheFirstCheck;
} else{ releaseToAddress() //first call
erc721Contract.attack() //this is our custom defined erc721. the attack function will call this function https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/CollateralToken.sol#L553 this overwrites our deleted underlyingid with our custom contract, so nothing reverts

}
}

Through this malicious contract, call safeTransferFrom() function in ClearingHouse pass in, encoded metadata (which contains our malicious contract's address uint256(bytes32(maliciousContractAddr)), the collateralId of the nft and then finally the two arguments to and from (it doesn't matter what they are, because they are unused throughout the code). This invokes the balanceOf twice. The second time we reenter, release the nft to the attacker, call into our erc721 contract which overwrites the underlyingid so that
commitToLien() can create LienToken for any holder
Audit: 2023-01-astaria-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

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

## Proof of Concept

The VaultImplementation.commitToLien() method is external and can be executed by anyone
The method will internally verify that the corresponding collateralId is yours or has the corresponding authorization
The validation code is as follows:
```solidity
  function _validateCommitment(
    if (
      msg.sender ! = holder &&
      receiver ! = holder && 
      receiver ! = operator &&
      !CT.isApprovedForAll(holder, msg.sender)
    ) {
      revert InvalidRequest(InvalidRequestReason.NO_AUTHORITY);
    }
```

Note that this receiver comes from the parameter, so anyone can pass receiver==holder to skip this authentication, i.e. anyone can createLien for the owner of the collateralId

There are many possible problems that can be caused:
For example:
1. if the collateralId has not been borrowed (only transfer NFT to CollateralToken), then the malicious user can generate a private vault, vault.asset as a worthless token, then set a very short borrowing period policy, and then through the above commitToLien() to borrow money for others
Since the period is very short, it will soon expire and enter the auction. Use worthless assets to bid to steal NFT

2. collateralId already has Lien, then the user can generate a very high interest rate strategy, and then generate Lien for someone else's borrowing through the above commitToLien(), thus earning interest

3. collateralId borrowed without knowing it

Suggestion:

Only verify msg.sender, do not verify receiver

## Tools Used

## Recommended Mitigation Steps

```solidity
  function _validateCommitment(
    IAstariaRouter.Commitment calldata params,
    address receiver
  ) internal view {
...
    if (
      msg.sender != holder &&
-     receiver != holder && 
-     receiver != operator &&
+     msg.sender != operator && 
      !CT.isApprovedForAll(holder, msg.sender)
    ) {
      revert InvalidRequest(InvalidRequestReason.NO_AUTHORITY);
    }  
```
ClearingHouse can pass in a malicious payment token
Audit: 2023-01-astaria-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
vault loss assets

## Proof of Concept

When the auction is successful the NFT is transferred to the bidder and seaport calls ClearingHouse.safeTransferFrom() to trigger the repayment of the debt through the conduit mechanism

ClearingHouse.safeTransferFrom() -> ClearingHouse._execute()
In _execute(), it does not check if msg.sender is from seaport, it only cares about the amount of payment received > currentOfferPrice
This is fine. It doesn't matter who calls it, as long as the amount is right

But here is a problem, only limit the number of amounts, but did not check whether the token address is legal, the address is a parameter, can be passed arbitrarily

```solidity
  function _execute(
    address tokenContract, // collateral token sending the fake nft
    address to, // buyer
    uint256 encodedMetaData, //retrieve token address from the encoded data
    uint256 // space to encode whatever is needed,
  ) internal {

    address paymentToken = bytes32(encodedMetaData).fromLast20Bytes();   <------From the parameter encodedMetaData, which can be forged arbitrarily

    uint256 currentOfferPrice = _locateCurrentAmount({
      startAmount: s.auctionStack.startAmount,
      endAmount: s.auctionStack.endAmount,
      startTime: s.auctionStack.startTime,
      endTime: s.auctionStack.endTime,
      roundUp: true //we are a consideration we round up
    });
   
    uint256 payment = ERC20(paymentToken).balanceOf(address(this));
```

And this paymentToken address, in the subsequent logic also did not verify whether this token address is the payment token specified when creating Liens


```solidity
  function _execute(
    address tokenContract, // collateral token sending the fake nft
    address to, // buyer
    uint256 encodedMetaData, //retrieve token address from the encoded data
    uint256 // space to encode whatever is needed,
  ) internal {
...
    ASTARIA_ROUTER.LIEN_TOKEN().payDebtViaClearingHouse(
      paymentToken,    <----Inside the payDebtViaClearingHouse() there is still no verification that the paymentToken address is legitimate
      collateralId,
      payment - liquidatorPayment,
      s.auctionStack.stack
    );  


    ASTARIA_ROUTER.COLLATERAL_TOKEN().settleAuction(collateralId);   <---- settleAuction() can pass ,it ok
``` 

To sum up:A malicious user can pass a worthless forged payment token to repay the vault, resulting in vault loss assets

## Tools Used

## Recommended Mitigation Steps

1._execute() add check payment token address




```solidity
  //ClearingHouse.sol
  function _execute(
    address tokenContract, // collateral token sending the fake nft
    address to, // buyer
    uint256 encodedMetaData, //retrieve token address from the encoded data
    uint256 // space to encode whatever is needed,
  ) internal {
...
    address paymentToken = byte
commitToLien() can pass in an illegal payment token
Audit: 2023-01-astaria-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/VaultImplementation.sol#L287-L306
https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/AstariaRouter.sol#L485-L486


# Vulnerability details

## Impact
Stealing vault assets

## Proof of Concept

There are currently two ways to create new Liens in the system
1. user call AstariaRouter.commitToLiens(), the internal implementation of this is through VaultImplementation.commitToLien() to create
2. user can call VaultImplementation.commitToLien() directly, because commitToLien is external(), as long as the owner of the NFT can call

Currently, the direct call to VaultImplementation.commitToLien() has a payment token legitimacy check vulnerability

Calling process:
VaultImplementation.commitToLien() -> VaultImplementation._requestLienAndIssuePayout() -> AstariaRouter.requestLienPosition() -> AstariaRouter._validateCommitment()
Set the payment token in _validateCommitment()
The code is as follows:
```solidity

  function _validateCommitment(
.....

      lien = ILienToken.Lien({
      collateralType: nlrType,
      details: details,
      strategyRoot: commitment.lienRequest.merkle.root,
      collateralId: commitment.tokenContract.computeId(commitment.tokenId),
      vault: commitment.lienRequest.strategy.vault,     <----Note that this vault parameter is passed in and can be specified arbitrarily, and can be specified as a malicious contract
      token: IAstariaVaultBase(commitment.lienRequest.strategy.vault).asset() <-----Specify vault as a malicious contract that can return a malicious payment token like:mock_token
    });
```  
There is no other place in the system to verify that this token is legitimate

But the malicious user receives the real asset token of the vault

VaultImplementation.commitToLien() -> VaultImplementation._requestLienAndIssuePayout()
```solidity

  function _requestLienAndIssuePayout(
...
    _validateCommitment(c, receiver);
    (newLienId, stack, slope) = ROUTER().requestLienPosition(c, recipient());
    payout = _handleProtocolFee(c.lienRequest.amount);
    ERC20(asset()).safeTransfer(receiver, payout);  <------use get real asset token
  }
```
LienToken.makePayment() is also repaid with the malicious payment token

In summary, a malicious user can  borrow vault's asset token, but  the malicious invalid payment token is repaid

## Tools Used

## Recommended Mitigation Steps

AstariaRouter.requestLienPosition() add check msg.sender == commitment.lienRequest.strategy.vault  to avoid malicious vault parameters


```solidity
  function requestLienPosition(
    IAstariaRouter.Commitment calldata params,
    address receiver
  )
    external
    whenNotPaused
    validVault(msg.sender)
    returns (
      uint256,
      ILienToken.Stack[] memory,
      uint256
    )
  {
+   require(msg.sender == params.lienRequest.strategy.vault,"invalid strategy.vault");

    RouterStorage s
User can lose 10 ethers to Vault
Audit: 2023-01-astaria-findings
Severity: HIGH

# Lines of code

https://github.com/AstariaXYZ/astaria-gpl/blob/f430f5b8fe868d4883a8249555df76ee7752587a/src/ERC4626-Cloned.sol#L38-L52


# Vulnerability details

## Impact

If a user or a contract that has a large allowance (10 ethers or max) on an ERC4626Cloned based Vault that has not yet received any deposits, calls [mint](https://github.com/AstariaXYZ/astaria-gpl/blob/f430f5b8fe868d4883a8249555df76ee7752587a/src/ERC4626-Cloned.sol#L38-L52) with 0 share argument, will have a 10 ethers of the asset transferred to the Vault with no way to reclaim it because he/it will receive no shares.

NOTE: Raising this issue as high risk conforming to judging criteria '[3 — High](https://docs.code4rena.com/awarding/judging-criteria#estimating-risk): Assets can be stolen/\***lost**\*/compromised directly'. No complains if judge feels that the necessary pre-conditions makes it a '[2 — Med](https://docs.code4rena.com/awarding/judging-criteria#estimating-risk)'.

## Proof of Concept

```solidity
    function testAssetsLostToVault() public {
        TestNFT nft = new TestNFT(1);
        address tokenContract = address(nft);
        uint256 tokenId = uint256(0);

        uint256 initialBalance = WETH9.balanceOf(address(this));

        // create a PublicVault with a 14-day epoch
        address publicVault =
            _createPublicVault({strategist: strategistOne, delegate: strategistTwo, epochLength: 14 days});

        PublicVault pv = PublicVault(publicVault);
        address joe = address(0x70a57ed);
        vm.deal(joe, 10 ether);
        vm.startPrank(joe);
        WETH9.deposit{value: 10 ether}();
        WETH9.approve(publicVault, 10 ether);
        uint256 assets = pv.mint(0, joe);

        emit log_string(string.concat("Joe's balance     : ", Strings.toString(WETH9.balanceOf(address(joe)))));
        emit log_string(string.concat("Joe's shares      : ", Strings.toString(pv.balanceOf(address(joe)))));
        emit log_string(string.concat("pv.totalAssets()  : ", Strings.toString(pv.totalAssets())));
        emit log_string(string.concat("pv.totalSupply()  : ", Strings.toString(pv.totalSupply())));
        emit log_string(string.concat("pv.previewRedeem(): ", Strings.toString(pv.previewRedeem(pv.balanceOf(address(joe))))));

        vm.stopPrank();
    }
```

Test results:

```
Running 1 test for src/test/AstariaTest.t.sol:AstariaTest
[PASS] testAssetsLostToVault() (gas: 1307633)
Logs:
  Joe's balance     : 0
  Joe's shares      : 0
  pv.totalAssets()  : 10000000000000000000
  pv.totalSupply()  : 0
  pv.previewRedeem(): 0

Test result: ok. 1 passed; 0 failed; finished in 29.48ms
```

## Tools Used
n/a

## Recommended Mitigation Steps

Since the previewMint does not use the original supply == 0 ? shares : ..., at least add a require precondition to the mint function to prevent minting of 0 shares.
Overflow/Underflow in interest calculation caused by lack of timestamp validation in `_getInterest()` & `_getRemainingInterest()` function
Audit: 2023-01-astaria-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

An attacker could manipulate the last timestamp of a lien in the stack struct to cause an underflow or overflow in the interest calculation. This could result in an incorrect interest amount being calculated, which could lead to incorrect payments being made or incorrect amounts of debt being reported. In a worst-case scenario, the impact of this attack is that the attacker can cause a financial loss to the contract owner or other users by manipulating the interest calculation to receive a larger amount of interest than they are actually owed.

## Proof of Concept

let's say again "Bob" is an attacker, here is a proof of concept example:

1.) Bob creates a malicious smart contract that calls the `_getInterest()` function with a lien last timestamp that is in the past, for example, ``block.timestamp - 10.``.

2.) The smart contract calculates the interest based on the difference between the current block timestamp and the last timestamp of the lien, which is stored in the stack struct. Since the last timestamp is in the past, the difference is negative, causing the interest calculation to underflow and resulting in a very small or even zero interest.

3.) Bob can then use this smart contract to make payments on a lien with a very small or zero interest, effectively stealing from the lien owner.

4.) The lien owner, who is assuming they will be charged interest on the loan, will be at a financial loss as they will not be receiving the expected interest payments.


## Tools Used

Manual Review 

## Recommended Mitigation Steps

```
    // Check if last timestamp must be in the future
    if (timestamp <= stack.point.last) {
              revert InvalidLastTimeStamp()
    }

    // Check if timestamp must not be in the past 
    if (timestamp > block.timestamp) {
               revert InvalidBlockTimestamp()
    }
        
    uint256 delta_t = stack.point.last - timestamp;
    return (delta_t * stack.lien.details.rate).mulWadDown(stack.point.amount);
}
```

the same implement can be set into `_getRemainingInterest()` function
Inconsistent min deposit
Audit: 2023-01-astaria-findings
Severity: HIGH

# Lines of code

https://github.com/AstariaXYZ/astaria-gpl/blob/f430f5b8fe868d4883a8249555df76ee7752587a/src/ERC4626-Cloned.sol#L129-L133


# Vulnerability details

## Impact

The first transfer in a vault that is based on ERC4626Cloned produces different results depending on whether it is done via deposit or via mint.
While first deposit produces a number of shares that is 1:1 with the asset, in the case of mint, the results may vary:
* if the user mints 1 share, he will  transfer 10 ethers of the asset setting a ratio of share:asset of 1 : 10 ethers
* if the user mints 10 ethers of shares, he will transfer 10 ethers of the asset setting a ration of share:asset of 1:1
* if the user mints 20 ethers of share, he will transfer 10 ethers of the asset setting a ratio of share:asset of 2:1

This means that if the first mint is for a small number of shares (say `N`), the minimum deposit from there on will be given by `10 ethers / N` or in other words the minimum transfer to produce at least 1 share will be very large.

## Proof of Concept

```solidity
    function testLargeTransferZeroShares() public {
        TestNFT nft = new TestNFT(1);
        address tokenContract = address(nft);
        uint256 tokenId = uint256(0);

        uint256 initialBalance = WETH9.balanceOf(address(this));

        // create a PublicVault with a 14-day epoch
        address publicVault =
            _createPublicVault({strategist: strategistOne, delegate: strategistTwo, epochLength: 14 days});

        PublicVault pv = PublicVault(publicVault);
        address joe = address(0x70a57ed);
        vm.deal(joe, 20 ether);
        vm.startPrank(joe);
        WETH9.deposit{value: 20 ether}();
        WETH9.approve(publicVault, 20 ether);
        uint256 assets = pv.mint(1, joe);

        vm.expectRevert(bytes("ZERO_SHARES"));
        pv.deposit(9.99 ether, joe);

        vm.stopPrank();
    }
```

Test results:

```
Running 1 test for src/test/AstariaTest.t.sol:AstariaTest
[PASS] testLargeTransferZeroShares() (gas: 1351952)
Test result: ok. 1 passed; 0 failed; finished in 13.69ms
```

## Tools Used
n/a

## Recommended Mitigation Steps

Mind and deposit previews are calculated using the following formulas.
```solidity
previewMint(uint256 shares)    => totalSupply() == 0 ? 10e18 : shares * totalAssets() / totalSupply();
previewDeposit(uint256 assets) => totalSupply() == 0 ? assets : assets * totalSupply() / totalAssets();

Either substitute `10e18` with the original `shares` in previewMint or ensure that if totalSupply is zero, then the number of shares requested (or issued) is also a large, matching value in order to produce a 1:1 initial ratio.
```