xanoutas/solidity-security-findings huggingface dataset:
Use safeTransferFrom instead of transferFrom for ERC721 transfers
Audit: 2022-07-golom-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L301
https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L361


# Vulnerability details

## Impact
The transferFrom() method is used instead of safeTransferFrom(), presumably to save gas. 
I however argue that this isn’t recommended because:
1 OpenZeppelin’s documentation discourages the use of transferFrom(), use safeTransferFrom() whenever possible
https://docs.openzeppelin.com/contracts/4.x/api/token/erc721#IERC721-transferFrom-address-address-uint256-
2 Given that any NFT can be used for the call option, there are a few NFTs (here’s an example) that have logic in the onERC721Received() function, which is only triggered in the safeTransferFrom() function and not in transferFrom()

## Proof of Concept
https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L301
https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L361

## Tools Used
visual scanning

## Recommended Mitigation Steps
Call the safeTransferFrom() method instead of transferFrom() for NFT transfers.
Delegation grief attack
Audit: 2022-07-golom-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L99


# Vulnerability details

## Impact
When delegating voting power to a user, the contract stores a checkpoint with all delegated tokens in an array `uint256[] delegatedTokenIds`. 

When we want to delegate a new `tokenId` to `toTokenId`, the contract requires that the array length stays under `500`:
```js
require(_delegatedTokenIds.length < 500, 'VVDelegation: Cannot stake more');
```
This means that it's possible for someone to delegate `500` tokens to a victim and stop further delegations. Notice that there's no floor cost for this attack beside gas costs (since `MIN_VOTING_POWER_REQUIRED = 0`).

## Proof of Concept
https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L99

## Recommended Mitigation Steps
Consider changing `MIN_VOTING_POWER_REQUIRED` to a relatively high value.
ERC721 unsafe transfers may result in tokens being lost.
Audit: 2022-07-golom-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/core/GolomTrader.sol#L236


# Vulnerability details

## Impact
According to Openzeppelin's [`ERC721.sol::_safeTransfer()`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ce0068c21ecd97c6ec8fb0db08570f4b43029dde/contracts/token/ERC721/ERC721.sol#L186)
```shell
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
```
## Proof of Concept
```solidity
  GolomTrader.sol::236 => ERC721(o.collection).transferFrom(o.signer, receiver, o.tokenId);
  GolomTrader.sol::301 => nftcontract.transferFrom(msg.sender, o.signer, o.tokenId);
  GolomTrader.sol::361 => nftcontract.transferFrom(msg.sender, o.signer, tokenId);
```
## Tools Used
Manual.
## Recommended Mitigation Steps
Use OpenZeppelin's `safeTransferFrom` to prevent NFT's from being lost.
`call()` should be used instead of `transfer()` on an `address payable`
Audit: 2022-07-golom-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L151-L156


# Vulnerability details

## Impact
The use of the deprecated `transfer()` function for an address will inevitably make the transaction fail when:

The claimer smart contract does not implement a payable function.
The claimer smart contract does implement a payable fallback which uses more than 2300 gas unit.
The claimer smart contract implements a payable fallback function that needs less than 2300 gas units but is called through proxy, raising the call's gas usage above 2300.
Additionally, using higher than 2300 gas might be mandatory for some multisig wallets.

## Proof of Concept
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L151-L156

```solidity
function payEther(uint256 payAmt, address payAddress) internal { // @audit transfer => call
        if (payAmt > 0) {
            // if royalty has to be paid
            payable(payAddress).transfer(payAmt); // royalty transfer to royaltyaddress
        }
    }
```
## Tools Used
Manual.
## Recommended Mitigation Steps
I recommend using `call()` instead of `transfer()`
USE SAFETRANSFERFROM INSTEAD OF TRANSFERFROM FOR ERC721 TRANSFERS
Audit: 2022-07-golom-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L236
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L301
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L361


# Vulnerability details

## Details & Impact
The `transferFrom()` method is used instead of `safeTransferFrom()`, presumably to save gas. We can state that this isn’t recommended because:

- OpenZeppelin’s documentation discourages the use of **transferFrom()**, use **safeTransferFrom()** whenever possible
- Given that any NFT can be used for the call option, there are a few NFTs (here’s an example) that have logic in the onERC721Received() function, which is only triggered in the safeTransferFrom() function and not in transferFrom()
- Past audits have seen same issue being exploited such as in : https://github.com/code-423n4/2022-05-cally-findings/issues/38

## POC
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L236
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L301
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L361
```
ERC721(o.collection).transferFrom(o.signer, receiver, o.tokenId);
```
Usage of transferFrom to send ERC721 tokens is discouraged
Audit: 2022-07-golom-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L236


# Vulnerability details

## Impact

ERC721 tokens can be locked forever if the receiver contract is unable to handle them

## Proof of Concept

The usage of `ERC721.transferFrom` to transfer NFTs  does not check if the receiver is able to handle them.

This means that the tokens can be locked forever.


        if (o.isERC721) {
                require(amount == 1, 'only 1 erc721 at 1 time');
                ERC721(o.collection).transferFrom(o.signer, receiver, o.tokenId);
            } else {
                ERC1155(o.collection).safeTransferFrom(o.signer, receiver, o.tokenId, amount, '');
            }

## Recommended Mitigation Steps

Use safeTransferFrom from OZ.
Usage of deprecated transfer to send ETH
Audit: 2022-07-golom-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L154


# Vulnerability details

## Impact

High dependency of gas in your contract

## Proof of Concept

The usage of transfer to send ETH is discouraged, because it sends a fixed amount of 2300 gas.  

This function was designed to avoid reentrancy however the dependency of gas could make revert the transaction is the gas cost changes.


        function payEther(uint256 payAmt, address payAddress) internal {
            if (payAmt > 0) {@audit gas
                // if royalty has to be paid
                payable(payAddress).transfer(payAmt);
            }
        }

## Recommended Mitigation Steps

Use call instead (your contract already has reentrancy protection)

     function payEther(uint256 payAmt, address payAddress) internal {
            if (payAmt > 0) {@audit gas
                // if royalty has to be paid
                payAddress.call{value:payAmt}("");
            }
        }
Extra ETH is not refunded in fillAsk
Audit: 2022-07-golom-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L217


# Vulnerability details



## Impact

Loss of funds when sending more ETH than swap requires.

## Proof of Concept

The function `_fillAsk` checks that the amount of ETH should be greater than the cost of the swap.       However, if the users incorrectly sends more ETH (or just miscalculate amount) then all remaining ETH will be locked in the contract forever


     // attached ETH value should be greater than total value of one NFT * total number of NFTs + any extra payment to be given
        require(msg.value >= o.totalAmt * amount + p.paymentAmt, 'mgmtm');

## Recommended Mitigation Steps

Refund the extra ETH sent by using a variable
Use of deprecated `transfer` function to send ETH
Audit: 2022-07-golom-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L154


# Vulnerability details

Using  `payable(address).transfer`  has been deprecated in favor of using  `.call{value:...}("")`  as the proper way of sending ETH. Using  `transfer`  or  `send`  will make transactions fail when the address corresponds to a contract that does not implement a payable function using less than 2300 gas (e.g. some multisigs or proxied contracts). As gas costs can change over time and smart contracts are increasingly used as investment aggregators / fund managers over EOAs, [it's recommended to stop using `transfer` altogether](https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/#:~:text=Why%20is%202300%20gas%20significant,()%20or%20send()%20methods.&text=Since%20its%20introduction%2C%20transfer(),helps%20guard%20against%20reentrancy%20attacks).

In the specific context of this project, current implementation will prevent such contracts from being able to correctly interact with the core functionality of this protocol, as the `payEther` function is used for all ETH transfers when filling orders and settling balances.
Use of transfer() instead of call() to send eth
Audit: 2022-07-golom-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/70aa26c4deec218891d3f1baf162d8a5fe2bc5e8/contracts/vote-escrow/VoteEscrowCore.sol#L1023
https://github.com/code-423n4/2022-07-golom/blob/70aa26c4deec218891d3f1baf162d8a5fe2bc5e8/contracts/core/GolomTrader.sol#L154
https://github.com/code-423n4/2022-07-golom/blob/70aa26c4deec218891d3f1baf162d8a5fe2bc5e8/contracts/rewards/RewardDistributor.sol#L151
https://github.com/code-423n4/2022-07-golom/blob/70aa26c4deec218891d3f1baf162d8a5fe2bc5e8/contracts/rewards/RewardDistributor.sol#L165
https://github.com/code-423n4/2022-07-golom/blob/70aa26c4deec218891d3f1baf162d8a5fe2bc5e8/contracts/rewards/RewardDistributor.sol#L208
https://github.com/code-423n4/2022-07-golom/blob/70aa26c4deec218891d3f1baf162d8a5fe2bc5e8/contracts/rewards/RewardDistributor.sol#L209


# Vulnerability details

# Use of transfer() instead of call() to send eth
## Impact
Use of transfer() might render ETH impossible to withdraw because after istanbul hardfork, there is increases in the gas cost of the SLOAD operation and therefore breaks some existing smart contracts.Those contracts will break because their fallback functions used to consume less than 2300 gas, and they’ll now consume more, since 2300 the amount of gas a contract’s fallback function receives if it’s called via Solidity’s transfer() or send() methods.

Any smart contract that uses transfer() or send() is taking a hard dependency on gas costs by forwarding a fixed amount of gas: 2300.

### References
https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/
https://blog.openzeppelin.com/opyn-gamma-protocol-audit/

## Proof of Concept
https://github.com/code-423n4/2022-07-golom/blob/70aa26c4deec218891d3f1baf162d8a5fe2bc5e8/contracts/vote-escrow/VoteEscrowCore.sol#L1023
https://github.com/code-423n4/2022-07-golom/blob/70aa26c4deec218891d3f1baf162d8a5fe2bc5e8/contracts/core/GolomTrader.sol#L154
https://github.com/code-423n4/2022-07-golom/blob/70aa26c4deec218891d3f1baf162d8a5fe2bc5e8/contracts/rewards/RewardDistributor.sol#L151
https://github.com/code-423n4/2022-07-golom/blob/70aa26c4deec218891d3f1baf162d8a5fe2bc5e8/contracts/rewards/RewardDistributor.sol#L165
https://github.com/code-423n4/2022-07-golom/blob/70aa26c4deec218891d3f1baf162d8a5fe2bc5e8/contracts/rewards/RewardDistributor.sol#L208
https://github.com/code-423n4/2022-07-golom/blob/70aa26c4deec218891d3f1baf162d8a5fe2bc5e8/contracts/rewards/RewardDistributor.sol#L209

### Recommended Mitigation Steps
Use call() to send eth
Call() should be used instead of transfer() on an address payable
Audit: 2022-07-golom-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L151-L156


# Vulnerability details

## Impact
When withdrawing ETH deposits, the payEther function in  **GolomTrader.sol** contract uses Solidity’s transfer function. This has some notable shortcomings which can render ETH deposits impossible to withdraw. Specifically, the withdrawal will inevitably fail when:

   1. The withdrawer smart contract does not implement a payable fallback function.
   2. The withdrawer smart contract implements a payable fallback function which uses more than 2300 gas units.
   3. The withdrawer smart contract implements a payable fallback function which needs less than 2300 gas units but is called through a proxy that raises the call’s gas usage above 2300.

## Proof of Concept
File: GolomTrader.sol [line 151-156](https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L151-L156)

```solidity
    function payEther(uint256 payAmt, address payAddress) internal {
        if (payAmt > 0) {
            // if royalty has to be paid
            payable(payAddress).transfer(payAmt); // royalty transfer to royaltyaddress
        }
    }
```

## Tools Used
Visual Studio code

## Recommended Mitigation Steps
Use call or  the sendValue function available in OpenZeppelin Contract’s Address library  which can be used to transfer Ether without being limited to 2300 gas units. Risks of reentrancy stemming from the use of this function can be mitigated by tightly following the “Check-effects-interactions” pattern and using OpenZeppelin Contract’s ReentrancyGuard contract. For further reference on why using Solidity’s transfer is no longer recommended, refer to these articles:

 - [Stop using Solidity’s transfer now](https://diligence.consensys.net/blog/2019/09/stop-using-soliditys-transfer-now/)
- [Reentrancy after Istanbul](https://blog.openzeppelin.com/reentrancy-after-istanbul/)
Upgraded Q -> 2 from #913 [1708535839185]
Audit: 2024-01-salty-findings
Severity: HIGH

Judge has assessed an item in Issue #913 as 2 risk. The relevant finding follows:

 [L-08] First staker can claim all rewards from period before
StakingRewards Linhe 78

Issue Description:

In the Salty protocol’s StakingRewards contract, there is an issue related to the distribution of rewards when no stakers are present. When rewards are distributed during a period with no stakers and a user subsequently stakes SALT, the implementation of _increaseUserShare() results in the first staker claiming all the rewards accumulated during the staker-less period:`

// Determine the amount of virtualRewards to add based on the current ratio of rewards/shares.
// The ratio of virtualRewards/increaseShareAmount is the same as totalRewards/totalShares for the pool.
// The virtual rewards will be deducted later when calculating the user's owed rewards.
if ( existingTotalShares != 0 ) // prevent / 0
	{
	// Round up in favor of the protocol.
	uint256 virtualRewardsToAdd = Math.ceilDiv( totalRewards[poolID] * increaseShareAmount, existingTotalShares );

	user.virtualRewards += uint128(virtualRewardsToAdd);
	totalRewards[poolID] += uint128(virtualRewardsToAdd);
	}

// Update the deposit balances
user.userShare += uint128(increaseShareAmount);
totalShares[poolID] = existingTotalShares + increaseShareAmount;

emit UserShareIncreased(wallet, poolID, increaseShareAmount);
}
The current logic is designed to distribute virtual rewards proportionally among stakers. However, to prevent division by zero, the function inadvertently allocates all accumulated rewards to the first staker in cases where they are the first to stake after a period with no stakers.

Recommended Mitigation Steps

To address this issue, the reward distribution mechanism should be adjusted to withhold rewards during periods with no stakers. The rewards accumulated during these periods should be retained by the rewards distributor and only begin to be distributed again once stakers are present. This change ensures that rewards are fairly allocated among stakers and not disproportionately claimed by a single user due to a timing advantage.
Upgraded Q -> 2 from #957 [1708275316654]
Audit: 2024-01-salty-findings
Severity: HIGH

Judge has assessed an item in Issue #957 as 2 risk. The relevant finding follows:

 function testVirtualRewardOverflow() public {
  // prepare arrays that are used multiple times in the test
  AddedReward[] memory addedRewards = new AddedReward[](1);
  addedRewards[0] = AddedReward(poolIDs[0], 10 ether);
  bytes32[] memory claimPools = new bytes32[](1);
  claimPools[0] = poolIDs[0];

  uint256 cooldown = stakingConfig.modificationCooldown();

  // Alice stakes
  vm.prank(DEPLOYER);
  stakingRewards.externalIncreaseUserShare(alice, poolIDs[0], 1, true);

  // add rewards
  stakingRewards.addSALTRewards(addedRewards);

  vm.warp(block.timestamp + cooldown);

  // Alice stakes
  vm.prank(DEPLOYER);
  // approx. 7.5 * 1e18 (value was found using Foundry fuzzing)
  stakingRewards.externalIncreaseUserShare(alice, poolIDs[0], 7488136320796573910, true);

  vm.warp(block.timestamp + cooldown);

  // Alice stakes, causing an overflow
  vm.expectRevert(stdError.arithmeticError);
  vm.prank(DEPLOYER);
  // approx. 94.6 * 1e18 (value was found using Foundry fuzzing)
  stakingRewards.externalIncreaseUserShare(alice, poolIDs[0], 94596573755484965130, true);
}
Malicious user can atomically take up to 1% of rewards for all newly whitelisted pools, by using DUST LP
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/DAO.sol#L235-L273
https://github.com/code-423n4/2024-01-salty/blob/main/src/Upkeep.sol#L205-L213
https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingRewards.sol#L182-L206


# Vulnerability details

## Impact

When pools are first whitelisted, a malicious user can atomically take up to 1% of the SALT emissions rewards for those pools in a single tx using DUST amount of LP. Since bootstrapping rewards per pool is defaulted to 200_000 SALT, and since whitelisting a token means whitelisting both the token-WBTC and token-WETH pool, the user can take up to 2_000 `*` 2 = 4_000 SALT in a single tx. This can be done by putting at little as 101 wei of both tokens into each pool to create LP, meaning there is no cost to the user.

## Proof of Concept

When a ballot for whitelisting a new token in the DAO contract can be finalized, a malicious user will craft a single transaction:
1. call [`DAO:finalizeBallot`](https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/DAO.sol#L278-L291) which triggers [`_finalizeTokenWhitelisting`](https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/DAO.sol#L235-L273). As part of the logic of this function, the following code is run:
```solidity
poolsConfig.whitelistPool( pools,  IERC20(ballot.address1), exchangeConfig.wbtc() );
poolsConfig.whitelistPool( pools,  IERC20(ballot.address1), exchangeConfig.weth() );
...
liquidityRewardsEmitter.addSALTRewards( addedRewards );
```
The [`RewardsEmitter:addSALTRewards`](https://github.com/code-423n4/2024-01-salty/blob/main/src/rewards/RewardsEmitter.sol#L57-L77) function then updates the pending rewards for both these whitelisted pools:
```solidity
pendingRewards[ addedReward.poolID ] += amountToAdd;
```

2. As both the token-WBTC and token-WETH pools are now whitelisted, add liquidity for both pools equal to 101 wei of both tokens. This passes the check that added token amounts are > DUST.

3. Call `Upkeep:performUpkeep`, which as part of its flow will trigger [`Upkeep:step8`](https://github.com/code-423n4/2024-01-salty/blob/main/src/Upkeep.sol#L205-L213), which has the following logic:
```solidity
uint256 timeSinceLastUpkeep = block.timestamp - lastUpkeepTimeRewardsEmitters;
...
saltRewards.liquidityRewardsEmitter().performUpkeep(timeSinceLastUpkeep);
```
This in turn triggers [`RewardsEmitter:performUpkeep`](https://github.com/code-423n4/2024-01-salty/blob/main/src/rewards/RewardsEmitter.sol#L82-L137), which loops over all whitelisted `PoolID`s with the following logic:
```solidity
...
if ( timeSinceLastUpkeep >= MAX_TIME_SINCE_LAST_UPKEEP )
   timeSinceLastUpkeep = MAX_TIME_SINCE_LAST_UPKEEP;

// These are the AddedRewards that will be sent to the specified StakingRewards contract
AddedReward[] memory addedRewards = new AddedReward[]( poolIDs.length );

// Rewards to emit = pendingRewards * timeSinceLastUpkeep * rewardsEmitterDailyPercent / oneDay
uint256 numeratorMu
The shares cooldown can cause more bad debt to be created
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/stable/CollateralAndLiquidity.sol#L154


# Vulnerability details

When a user is liquidated, `_decreaseUserShare()` is called and useCooldown is set to true. This can create problems because liquidations need to happen immediately and in case of unpredictable events the cooldown can just prevent users for being liquidated for an hour. This can create more bad debt because the users will not get liquidated until the cooldown expires. 

## Impact
Liquidators will have to wait until the cooldown expires which is currently set to 1 hour. This can create more bad debt and cause more losses to the protocol. 

## Proof of Concept

```solidity
_decreaseUserShare( wallet, collateralPoolID, userCollateralAmount, true );
```

As you can see in `liquidateUser()`, useCooldown is set true which will make the whole transaction revert if the cooldown didnt expire yet. 


## Tools Used
Manual Review

## Recommended Mitigation Steps
Liquidations need to happen immediately so consider creating a smaller cooldown just for liquidations so for example 3 minutes(from the last time the user increased their shares)


## Assessed type

Other