xanoutas/solidity-security-findings huggingface dataset:
SGOracle.sol LP price calculation is incorrect
Audit: 2023-07-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph-audit/blob/023751a4e987cf7c203ab25d3abba58f7344f213/contracts/oracle/implementations/SGOracle.sol#L49


# Vulnerability details

## Impact

SGOracle.sol LP price calculation is incorrect

## Proof of Concept

In SGOracle.sol, the [1 LP token price is calculated as](https://github.com/Tapioca-DAO/tapioca-periph-audit/blob/023751a4e987cf7c203ab25d3abba58f7344f213/contracts/oracle/implementations/SGOracle.sol#L49) 

```solidity
    /// @notice Calculated the price of 1 LP token
    /// @return _maxPrice the current value
    /// @dev This function comes from the implementation in vyper that is on the bottom
    function _get() internal view returns (uint256 _maxPrice) {
        uint256 lpPrice = (SG_POOL.totalLiquidity() *
            uint256(UNDERLYING.latestAnswer())) / SG_POOL.totalSupply();

        return lpPrice;
    }
```

the UNDERLYING.latestAnswer() would get a asset price from chainlink oracle

However, We cannot use SG_POOL.totalLiquidity to muliple the asset price because the SG_POOL.totalLiquidity cannot give us the total amount of the asset in the Stargate pool

Let us the Stargate ETH pool as an example:

https://etherscan.io/address/0x101816545F6bd2b1076434B54383a1E633390A2E

At the time of this report writing,

this pool hold 7081 SGETH

but the totalLiquidity returns 8905532473992063272988 at the moment of writing report

and totalSupply returns 8904805647445680879893 at the moment of writing report

if we use totalLiquidity / totalSupply, the result is roughly 1, this ratio applyes to other pool as well for example USDC pool 

https://etherscan.io/address/0x38ea452219524bb87e18de1c24d3bb59510bd783#readContract

then if we use the totalLiquidity * the asset price / totalSupply

then 1 LP token = 1 ETH asset price, this is not incorrect

https://etherscan.io/address/0x101816545F6bd2b1076434B54383a1E633390A2E#code#F1#L69

```solidity
    uint256 public totalLiquidity; // the total amount of tokens added on this side of the chain (fees + deposits - withdrawals)
```

as the current suggest, the totalLiquidity is the total amount of tokens added, but not in the unit of ETH

## Tools Used

Manual

## Recommended Mitigation Steps

To calculate the how much 1 LP price worth, I think the protocol should check how much liquidity token 1 LP token can withdraw, then convert the withdrawal liquidity to USD value to measure teh 1 LP token worth (please be carefully and implement it in a oracle-manipulation resist way)


## Assessed type

Oracle
maxBorrowPart is not scaled to 18 decimals before comparing it to closing factor (i.e. availableBorrowPart)
Audit: 2023-07-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/bigBang/BigBang.sol#L797-L799


# Vulnerability details

## Impact
In BigBang, `_updateBorrowAndCollateralShare` is used to deduct values from `userBorrowPart` and `userCollateralShare` on Liquidation.
`_updateBorrowAndCollateralShare` is called by `_liquidateUser` method which is called by other methods till we reach `liquidate`. Here is the flow:

 `liquidate` => `_closedLiquidation` => `_liquidateUser` => `_updateBorrowAndCollateralShare`
 
The liquidater can pass `maxBorrowAmount` when calling `liquidate` . This will be passed to  `_updateBorrowAndCollateralShare`. This method  **chooses maxBorrowPart if it is smaller than availableBorrowPart** 
```solidity
	borrowPart = maxBorrowPart > availableBorrowPart
		? availableBorrowPart
		: maxBorrowPart;
```
[Code link](https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/bigBang/BigBang.sol#L797-L799)

The issue is in the comparison here. **maxBorrowPart is not scaled (up or down) to 18 decimals unlike availableBorrowPart which is scaled already** in `computeClosingFactor`  method
```solidity
    function computeClosingFactor(
        uint256 borrowPart,
        uint256 collateralPartInAsset,
        uint256 borrowPartDecimals,
        uint256 collateralPartDecimals,
        uint256 ratesPrecision
    ) public view returns (uint256) {
        uint256 borrowPartScaled = borrowPart;
        if (borrowPartDecimals > 18) {
            borrowPartScaled = borrowPart / (10 ** (borrowPartDecimals - 18));
        }
        if (borrowPartDecimals < 18) {
            borrowPartScaled = borrowPart * (10 ** (18 - borrowPartDecimals));
        }
```
[Code link](https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/Market.sol#L256-L269)

If borrowAssetDecimals is lower than 18 then it is going to pick a wrong maxBorrow (most likely a **value lower** than what the liquidator intended. This leads to a possible loss of rewards for the liquidator since liquidations could already be finished by other liquidators).
If borrowAssetDecimals is greater than18 then it is also going to pick a wrong maxBorrow (most likely a **value higher** than what the liquidator intended).
In both scenarios, it is unfair to the liquidator.

From here you can imagine other impacts that can be caused by this issue. For example, all calculations later will also be wrong. For example, when converting to elastic, the amountWithBonus ...etc. 
```solidity
	borrowAmount = totalBorrow.toElastic(borrowPart, false);
	borrowAmount = totalBorrow.toElastic(borrowPart, false);
uint256 amountWithBonus = borrowAmount +
	(borrowAmount * liquidationMultiplier) /
	FEE_PRECISION;
```
[Code link](https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/bigBang/
Incorrect solvency check because it multiplies collateralizationRate by share not amount when calculating liquidation threshold
Audit: 2023-07-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/Market.sol#L402-L425


# Vulnerability details

## Impact
When a Collateralized Debt Position (CDP) reaches that liquidation threshold, it becomes eligible for liquidation and anyone can repay a position in exchange for a portion of the collateral. 
 `Market._isSolvent` is used to check if the user is solvent. if not, then it can be liquidated. Here is the method body:
 
```
function _isSolvent(
	address user,
	uint256 _exchangeRate
) internal view returns (bool) {
	// accrue must have already been called!
	uint256 borrowPart = userBorrowPart[user];
	if (borrowPart == 0) return true;
	uint256 collateralShare = userCollateralShare[user];
	if (collateralShare == 0) return false;

	Rebase memory _totalBorrow = totalBorrow;

	return
		yieldBox.toAmount(
			collateralId,
			collateralShare *
				(EXCHANGE_RATE_PRECISION / FEE_PRECISION) *
				collateralizationRate,
			false
		) >=
		// Moved exchangeRate here instead of dividing the other side to preserve more precision
		(borrowPart * _totalBorrow.elastic * _exchangeRate) /
			_totalBorrow.base;
}
```
[Code link](https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/Market.sol#L402C1-L425C6)


The issue is that the collateralizationRate is multiplied by collateralShare (with precision constants) then converted to amount. This is incorrect, **the collateralizationRate sholud be used with amounts and not shares. Otherwise, we get wrong results**.
```solidity
yieldBox.toAmount(
			collateralId,
			collateralShare *
				(EXCHANGE_RATE_PRECISION / FEE_PRECISION) *
				collateralizationRate,
			false
		)
```

Please note that when using shares **it is not in favour of the protocol**, so amounts should be used instead. The only case where this is ok, is when the share/amount ratio is 1:1 which can not be, because totalAmount always get +1 and totalShares +1e8 to prevent 1:1 ratio type of attack.
```solidity
    function _toAmount(
        uint256 share,
        uint256 totalShares_,
        uint256 totalAmount,
        bool roundUp
    ) internal pure returns (uint256 amount) {
        // To prevent reseting the ratio due to withdrawal of all shares, we start with
        // 1 amount/1e8 shares already burned. This also starts with a 1 : 1e8 ratio which
        // functions like 8 decimal fixed point math. This prevents ratio attacks or inaccuracy
        // due to 'gifting' or rebasing tokens. (Up to a certain degree)
        totalAmount++;
        totalShares_ += 1e8;

```
[Code link](https://github.com/Tapioca-DAO/YieldBox/blob/f5ad271b2dcab8b643b7cf622c2d6a128e109999/contracts/YieldBoxRebase.sol#L47-L52)

Moreover, in the method `_computeMaxAndMinLTVInAsset` which is supposed to returns the min and max LTV for user in asset price. Amount is used and not share. Here is the code:
```
	function _computeMaxAndMinLTVInAsse
Inaccurate accrued Interest due to borrow fee amount inclusion which is unfair to borrowers
Audit: 2023-07-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/bigBang/BigBang.sol#L747-L755


# Vulnerability details

## Impact
In BigBang, a flat % fee is charged for any borrow. Therefore, when you borrow USDO, you are charged that fee (default is 0.05%). Now, when you borrow a certain amount, the amount is converted to part, then `userBorrowPart` and `totalBorrow` is updated 
```solidity
	uint256 feeAmount = (amount * borrowOpeningFee) / FEE_PRECISION; // A flat % fee is charged for any borrow

	(totalBorrow, part) = totalBorrow.add(amount + feeAmount, true);
	require(
		totalBorrowCap == 0 || totalBorrow.elastic <= totalBorrowCap,
		"BigBang: borrow cap reached"
	);

	userBorrowPart[from] += part; 
```
[Code link](https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/bigBang/BigBang.sol#L747-L755)

However, if you see the code above, it converts the amount+feeAmount to part before storing it. This means accrued interest is calculated based on not only the borrowed amount but also the feeAmount which is not fair. Accruing should be applied on the borrowed amount only.


## Proof of Concept

- When you run the PoC below, you will get the following results:
```sh
[PASS] test_FeeAmountAcrrued() (gas: 102592)
Logs:
  ===BOB BORROW===
  Amount: 1000000000000000000
  Borrow Fee: 500000000000000
  UserBorrowPart: 1000500000000000000
  Total Borrow Base: 1000500000000000000
  Total Borrow Elastic: 1000500000000000000
  ===356 days passed with fee accruing===
  Total Borrow Elastic: 1005488794491636883

[PASS] test_FeeAmountNotAcrrued() (gas: 102559)
Logs:
  ===ALICE BORROW===
  Amount: 1000000000000000000
  Borrow Fee: 500000000000000
  UserBorrowPart: 1000000000000000000
  Total Borrow Base: 1000000000000000000
  Total Borrow Elastic: 1000000000000000000
  ===356 days passed without fee accruing===
  Total Borrow Elastic: 1004986301340966400

Test result: ok. 2 passed; 0 failed; finished in 1.66ms
```

As you can see Total Borrow Elastic without fee accruing is less than Total Borrow Elastic with fee accruing. 


- Code: Please note some lines in borrow method were commented out for simplicity. It is irrelevant anyway, since we are interested in elastic calculations. So we are safe.
```solidity
// PoC => BIGBANG : shouldn't accrue borrow fee 
// Command => forge test -vv
pragma solidity >=0.8.4 <0.9.0;

import {Test} from "forge-std/Test.sol";
import "forge-std/console.sol";

import {DSTest} from "ds-test/test.sol";

struct AccrueInfo {
    uint64 debtRate;
    uint64 lastAccrued;
}

struct Rebase {
    uint128 elastic;
    uint128 base;
}

/// @notice A rebasing library using overflow-/underflow-safe math.
library RebaseLibrary {
    /// @notice Calculates the base value in relationship to `elastic` and `total`.
    function toBase(
        Rebase memory total,
        uint256 elastic,
        bool roundUp
    ) internal pu
Inaccurate/Incorrect calculation of returned shares by `_borrow` method
Audit: 2023-07-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/bigBang/BigBang.sol#L760-L764


# Vulnerability details

## Impact
In BigBang, `_borrow` method is used to do the actual borrowing as follows:
1. Convert amount to part then update userBorrowPart.
2. mint USDO.
3. Deposit borrowed amount into yieldBox for the user.
4. Return part and share.

We are interested in the last two points. Let's have a look at the code:
```solidity
	//deposit borrowed amount to user
	asset.approve(address(yieldBox), amount);
	yieldBox.depositAsset(assetId, address(this), to, amount, 0);

	share = yieldBox.toShare(assetId, amount, false);
```
[Code Link](https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/bigBang/BigBang.sol#L760-L764)

As noticed the share is calculated after `yieldBox.depositAsset` call is done. This calculation is incorrect because it happens after the deposit where the totalSupply and totalAmount changed already. As you may know, totalSupply and totalAmount is a part of the math where the share is calculated. 
```solidity
	// Effects
	uint256 totalAmount = _tokenBalanceOf(asset);
	if (share == 0) {
		// value of the share may be lower than the amount due to rounding, that's ok
		share = amount._toShares(totalSupply[assetId], totalAmount, false);

```
[Code link](https://github.com/Tapioca-DAO/YieldBox/blob/f5ad271b2dcab8b643b7cf622c2d6a128e109999/contracts/YieldBox.sol#L130-L133)

Instead, the returned share from `yieldBox.depositAsset` should be returned. This is because the calculation happens before minting the share and transferring the tokens.
```solidity
	.
	.
	// Effects
	uint256 totalAmount = _tokenBalanceOf(asset);
	if (share == 0) {
		// value of the share may be lower than the amount due to rounding, that's ok
		share = amount._toShares(totalSupply[assetId], totalAmount, false);
	} else {
		// amount may be lower than the value of share due to rounding, in that case, add 1 to amount (Always round up)
		amount = share._toAmount(totalSupply[assetId], totalAmount, true);
	}
	_mint(to, assetId, share);
	// Interactions
	if (asset.tokenType == TokenType.ERC20) {
		// For ERC20 tokens, use the safe helper function to deal with broken ERC20 implementations. This actually calls transferFrom on the ERC20 contract.
		IERC20(asset.contractAddress).safeTransferFrom(from, address(asset.strategy), amount);
	.
	.
```
[Code link](https://github.com/Tapioca-DAO/YieldBox/blob/f5ad271b2dcab8b643b7cf622c2d6a128e109999/contracts/YieldBox.sol#L130-L153)

The method `_borrow` is internal and called by two external methods:
1. `borrow`  in this case, the impact is low since it probably would effect the UI only. However, if `borrow` is called by a contract, and that contract is relying on the returned share then it opens the door for several impacts, depending on the logic in that contract. simply because the returned share is
Flash loan contracts can manipulate liquidations.
Audit: 2023-07-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/singularity/Singularity.sol#L440-L459


# Vulnerability details

## Impact
A malicious contract could: 
1.	Take a flash loan
2.	Call liquidate(), passing manipulated arrays to liquidate honest users
3.	Profit from bonuses
4.	Repay the flash loan


## Proof of Concept
The liquidate() function takes array parameters that specify which users to liquidate and how much to liquidate. There are no checks on the caller of liquidate() - any contract can call it.

A flash loan contract could manipulate the arrays passed into liquidate() to liquidate arbitrary users. For example, a malicious contract could take a flash loan, call liquidate() to liquidate honest users, profit from bonuses, and repay the flash loan.

There are no checks like: 
o	Ensuring the caller has skin in the game / collateral
o	Checking the caller is not a contract
o	Limiting liquidatable amounts per call


## Tools Used
Manual
## Recommended Mitigation Steps 
Add a onlyEOA modifier to liquidate() to prevent contracts from calling it


## Assessed type

Other
Ability to steal user funds and increase collateral share infinitely in BigBang and Singularity
Audit: 2023-07-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/bigBang/BigBang.sol#L288
https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/singularity/SGLCollateral.sol#L27


# Vulnerability details

The `addCollateral` methods in both BigBang and Singularity contracts allow the share parameter to be passed as `0`. When `share` is `0`, the equivalent amount of shares is calculated using the YieldBox `toShare` method. However, there is a modifier named `allowedBorrow` that is intended to check the allowed borrowing amount for each implementation of the `addCollateral` methods. Unfortunately, the modifier is called with the `share` value passed to `addCollateral`, and in the case of `0`, it will always pass.

```solidity
// MarketERC20.sol
function _allowedBorrow(address from, uint share) internal {
    if (from != msg.sender) {
        if (allowanceBorrow[from][msg.sender] < share) {
            revert NotApproved(from, msg.sender);
        }
        allowanceBorrow[from][msg.sender] -= share;
    }
}

// BigBang.sol
function addCollateral(
    address from,
    address to,
    bool skim,
    uint256 amount,
    uint256 share
    // @audit share is calculated afterwords the modifier
) public allowedBorrow(from, share) notPaused {
    _addCollateral(from, to, skim, amount, share);
}

function _addCollateral(
    address from,
    address to,
    bool skim,
    uint256 amount,
    uint256 share
) internal {
    if (share == 0) {
        share = yieldBox.toShare(collateralId, amount, false);
    }
    userCollateralShare[to] += share;
    uint256 oldTotalCollateralShare = totalCollateralShare;
    totalCollateralShare = oldTotalCollateralShare + share;
    _addTokens(
        from,
        to,
        collateralId,
        share,
        oldTotalCollateralShare,
        skim
    );
    emit LogAddCollateral(skim ? address(yieldBox) : from, to, share);
}
```

This leads to various critical scenarios in BigBang and Singularity markets where user assets can be stolen, and collateral share can be increased infinitely which in turn leads to infinite USDO borrow/mint and borrowing max assets from Singularity market.

Refer to [Proof of Concept](#proof-of-concept) for attack examples

## Impact
High - allows stealing of arbitrary user yieldbox shares in BigBang contract and Singularity. In the case of BigBang this leads to infinite minting of USDO. Effectively draining all markets and LPs where USDO has value. In the case of Singularity this leads to infinite borrowing, allowing an attacker to obtain possession of all other users' collateral in Singularity.

## Proof of concept
1. Malicious actor can add any user shares that were approved to BigBang or Singularity contracts deployed. This way adversary is stealing user shares that he can unwrap to get underlying collateral provided. 

```solidity
it('allows steal other user YieldB
Arbitrary `delegatecall` allows stealing of funds from `BaseTOFTLeverageModule`
Audit: 2023-07-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tapiocaz-audit/blob/master/contracts/tOFT/modules/BaseTOFTLeverageModule.sol#L184


# Vulnerability details

## Impact

Stealing of funds.

## Proof of Concept

There is a `public` function in `BaseTOFTLeverageModule` contract: `leverageDown`.

Issue here is there is:

* No Access control (public function).
* No Validation on `module` address.

```solidity

  function leverageDown(
        address module,
        uint16 _srcChainId,
        bytes memory _srcAddress,
        uint64 _nonce,
        bytes memory _payload
    ) public {
        //-----SNIP: Decode _payload----//

        uint256 balanceBefore = balanceOf(address(this));
        bool credited = creditedPackets[_srcChainId][_srcAddress][_nonce];
        if (!credited) {
            _creditTo(_srcChainId, address(this), amount);
            creditedPackets[_srcChainId][_srcAddress][_nonce] = true;
        }
        uint256 balanceAfter = balanceOf(address(this));

@->     (bool success, bytes memory reason) = module.delegatecall(
            abi.encodeWithSelector(
                this.leverageDownInternal.selector,
                amount,
                swapData,
                externalData,
                lzData,
                leverageFor
            )
        );

        if (!success) {
            if (balanceAfter - balanceBefore >= amount) {
                IERC20(address(this)).safeTransfer(leverageFor, amount);
            }
            revert(_getRevertMsg(reason)); //forward revert because it's handled by the main executor
        }

        emit ReceiveFromChain(_srcChainId, leverageFor, amount);
    }

```
[Link to Code](https://github.com/Tapioca-DAO/tapiocaz-audit/blob/master/contracts/tOFT/modules/BaseTOFTLeverageModule.sol#L184)

Leveraging these 2 issues, any attacker can use the following step to exploit the vulnerability:

1. An attacker can deploy their own malicious `module` contract with a `leverageDownInternal` function.
2. An attacker can call the `leverageDown` function, passing their malicious module's address.
3. The malicious `leverageDownInternal` function is designed to transfer funds from the `BaseTOFTLeverageModule` contract to the attacker's address.

This way the attacker's module can exploit the unprotected `delegatecall` to withdraw funds from the `BaseTOFTLeverageModule` contract without proper authorization.

## Tools Used

VS Code

## Recommended Mitigation Steps

Have an access control on the function or validate the `module` address parameter before performing any critical operations.


## Assessed type

call/delegatecall