xanoutas/solidity-security-findings huggingface dataset:
Incorrect use of `state.isDelinquent` in `updateTimeDelinquentAndGetPenaltyTime` function - total misbehavior of applying delinquency fee
Audit: 2024-08-wildcat-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-wildcat/blob/main/src/libraries/FeeMath.sol#L96
https://github.com/code-423n4/2024-08-wildcat/blob/main/src/market/WildcatMarketBase.sol#L406


# Vulnerability details

## Impact
Due to the incorrect value of `state.isDelinquent`, the `updateTimeDelinquentAndGetPenaltyTime` function malfunctions, causing significant disruptions in fee accumulation.

## Proof of Concept
Over time, as market operations occur, borrowers accumulate fees. This process is managed by the `FeeMath.updateScaleFactorAndFees` function. While this function updates the scaleFactor and applies the protocolFee, it also implements the delinquency fee based on the market's delinquency status. The calculation is performed by the `updateDelinquency` function, which then utilizes the `updateTimeDelinquentAndGetPenaltyTime` function to determine the duration for which the `delinquencyFeeBips` should be applied.

This function has a critical flaw by using incorrect `state.isDelinquent` value. It's assumed that:
1. `state.isDelinquent` is up-to-update
2. The delinqency state of market has been consistent (state.isDelinquent) since last update until current timestamp.

```solidity
  function updateTimeDelinquentAndGetPenaltyTime(
    MarketState memory state,
    uint256 delinquencyGracePeriod,
    uint256 timeDelta
  ) internal pure returns (uint256 /* timeWithPenalty */) {
    // Seconds in delinquency at last update
    uint256 previousTimeDelinquent = state.timeDelinquent;

>>   if (state.isDelinquent) {

    ...
  }
```

However, this is WRONG because
- `state.isDelinquent` is the delinquency status of last updated time (indicated by `state.lastInterestAccruedTimestamp`)
- It is only updated when calling `_writeState` function which is normally at the end of an action.

So the whole deliquent penalty time is calculated wrongly, which leads to incorrect accumulation of delinquency fee.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Though we can't completely guarantee 2nd point (`The delinqency state of market has been consistent`) because delinquency state can be changed in-between by direct transfer-in of assets (either by borrower or whoever), we can mostly assume it's true for a specific duration between market operations.

We can have correct value of `state.isDelinquent`, so that `updateTimeDelinquentAndGetPenaltyTime` function could perform correctly. It can be done by making changes to `_getUpdatedState` function of `WildcatMarketBase` contract.

```diff
  function _getUpdatedState() internal returns (MarketState memory state) {
    state = _state;
+   bool isDelinquent = state.liquidityRequired() > totalAssets();
+   state.isDelinquent = isDelinquent;
    ...
  }
```


## Assessed type

Error
The protocol has not charged the protocol fee.
Audit: 2024-08-wildcat-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-wildcat/blob/fe746cc0fbedc4447a981a50e6ba4c95f98b9fe1/src/libraries/MarketState.sol#L15


# Vulnerability details

## Proof of Concept
The protocol has set `accruedProtocolFees` to represent the current protocol fees and `protocolFeeBips` to represent the protocol fee rate. 
```solidity
struct MarketState {
  bool isClosed;
  uint128 maxTotalSupply;
  uint128 accruedProtocolFees;
  // Underlying assets reserved for withdrawals which have been paid
  // by the borrower but not yet executed.
  uint128 normalizedUnclaimedWithdrawals;
  // Scaled token supply (divided by scaleFactor)
  uint104 scaledTotalSupply;
  // Scaled token amount in withdrawal batches that have not been
  // paid by borrower yet.
  uint104 scaledPendingWithdrawals;
  uint32 pendingWithdrawalExpiry;
  // Whether market is currently delinquent (liquidity under requirement)
  bool isDelinquent;
  // Seconds borrower has been delinquent
  uint32 timeDelinquent;
  // Fee charged to borrowers as a fraction of the annual interest rate
  uint16 protocolFeeBips;
  // Annual interest rate accrued to lenders, in basis points
  uint16 annualInterestBips;
  // Percentage of outstanding balance that must be held in liquid reserves
  uint16 reserveRatioBips;
  // Ratio between internal balances and underlying token amounts
  uint112 scaleFactor;
  uint32 lastInterestAccruedTimestamp;
}
```
It also has related functions set up to update protocolFeeBips and collet protocol fee
github:[https://github.com/code-423n4/2024-08-wildcat/blob/fe746cc0fbedc4447a981a50e6ba4c95f98b9fe1/src/market/WildcatMarketConfig.sol#L164](https://github.com/code-423n4/2024-08-wildcat/blob/fe746cc0fbedc4447a981a50e6ba4c95f98b9fe1/src/market/WildcatMarketConfig.sol#L164)
```solidity
  function setProtocolFeeBips(
    uint16 _protocolFeeBips
  ) external nonReentrant sphereXGuardExternal {
    if (msg.sender != factory) revert_NotFactory();
    if (_protocolFeeBips > 1_000) revert_ProtocolFeeTooHigh();
    MarketState memory state = _getUpdatedState();
    if (state.isClosed) revert_ProtocolFeeChangeOnClosedMarket();
    if (_protocolFeeBips == state.protocolFeeBips) revert_ProtocolFeeNotChanged();
    hooks.onSetProtocolFeeBips(_protocolFeeBips, state);
    state.protocolFeeBips = _protocolFeeBips;
    emit ProtocolFeeBipsUpdated(_protocolFeeBips);
    _writeState(state);
  }

```
```solidity
  /**
   * @dev Withdraw available protocol fees to the fee recipient.
   */
  function collectFees() external nonReentrant sphereXGuardExternal {
    MarketState memory state = _getUpdatedState();
    if (state.accruedProtocolFees == 0) revert_NullFeeAmount();

    uint128 withdrawableFees = state.withdrawableProtocolFees(totalAssets());
    if (withdrawableFees == 0) revert_InsufficientReservesForFeeWithdrawal();

    state.accruedProtocolFees -= withdrawableFees;
    asset.safeTransfer(feeRecipient, withdrawableFees);
    _writeState(state);
    emit_FeesCollected(withdrawa
SmartAccountFactory.sol - Account can be created for an owner and setting any entryPoint
Audit: 2023-01-biconomy-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccountFactory.sol#L33
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L166


# Vulnerability details

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

## Proof of Concept
The entrypoint is being set in the `init(args...)` function of SmartAccount.sol. The problem is that the malicious users could create wallets for legitimate owners of wallets and set the entrypoint contract to be anything they want. 

What's more, a wallet creation for a user could be frontran by a malicious actor, returning exactly the same address as expected, but with a different entry point. The reason is that the entryPoint is not being used as the salt.

```
    function init(address _owner, address _entryPointAddress, address _handler) public override initializer { 
        require(owner == address(0), "Already initialized");
        require(address(_entryPoint) == address(0), "Already initialized");
        require(_owner != address(0),"Invalid owner");
        require(_entryPointAddress != address(0), "Invalid Entrypoint");
        require(_handler != address(0), "Invalid Entrypoint");
        owner = _owner;
        _entryPoint =  IEntryPoint(payable(_entryPointAddress));
        if (_handler != address(0)) internalSetFallbackHandler(_handler);
           setupModules(address(0), bytes(""));
    }
```

```
    function deployCounterFactualWallet(address _owner, address _entryPoint, address _handler, uint _index) public returns(address proxy){
        bytes32 salt = keccak256(abi.encodePacked(_owner, address(uint160(_index))));
        bytes memory deploymentData = abi.encodePacked(type(Proxy).creationCode, uint(uint160(_defaultImpl)));
        // solhint-disable-next-line no-inline-assembly
        assembly {
            proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt)
        }
        require(address(proxy) != address(0), "Create2 call failed");
        // EOA + Version tracking
        emit SmartAccountCreated(proxy,_defaultImpl,_owner, VERSION, _index);
        BaseSmartAccount(proxy).init(_owner, _entryPoint, _handler);
        isAccountExist[proxy] = true;
    }
```

## Recommended Mitigation Steps
Add entrypoint to salt.
```
    function deployCounterFactualWallet(address _owner, address _entryPoint, address _handler, uint _index) public returns(address proxy){
        bytes32 salt = keccak256(abi.encodePacked(_owner, address(uint160(_index)), _entryPoint));
        bytes memory deploymentData = abi.encodePacked(type(Proxy).creationCode, uint(uint160(_defaultImpl)));
        // solhint-disable-next-line no-inline-assembly
        assembly {
            proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt)
        }
        require(address(proxy) != address(0), "Create2 call failed");
        // EOA + V
Relayers can steal extra fees from smart contract wallets on every transaction
Audit: 2023-01-biconomy-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L192-L219


# Vulnerability details

## Impact
Relayers can take signed transactions and append zeroes to the signature parameter to artificially increase the gas cost and startGas estimation. This  causes additional cost for the signer and increases the relayers reimbursement. The cost/reimbursement increases linearly with gas price.

## Proof of Concept
To calculate the refund payment SmartAccount uses a heuristic to estimate the startGas [(startGas = gasleft() + 21000 + msg.data.length * 8;)](https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L200). Later [it proceeds to subtract the remaining gas from this initial estimation](https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L239). By appending zeroes to the signature parameter, thus increasing the msg.data.length, the heuristic will overestimate the startGas, which in turn increases the reimbursement.

### Hardhat example
To see the impact of this manipulation add the following to test\smart-wallet\testGroup1.ts. I've chosen a relatively high gas price of 100 gwei and the most simple transaction of sending ether to show maximum impact of this finding. The loss scales linearly with gas price, but will typically be much less than shown here, because it assumes one transaction to fill an entire ethereum block. Though it should be feasible for relayers to submit such single transaction bundles to extract this value. On other blockchains with higher block size, the loss can also be increased.




    it("accepts manipulated transaction data", async function () {
      const ownerSCW = userSCW;

      const safeTx: SafeTransaction = buildSafeTransaction({
        to: charlie,
        value: ethers.utils.parseEther("1"),
        nonce: await ownerSCW.getNonce(0),
      });

      const gasEstimate1 = await ethers.provider.estimateGas({
        to: charlie,
        value: ethers.utils.parseEther("1"),
        from: ownerSCW.address,
      });

      const chainId = await ownerSCW.getChainId();

      safeTx.refundReceiver = "0x0000000000000000000000000000000000000000";
      safeTx.gasToken = "0x0000000000000000000000000000000000000000";
      safeTx.gasPrice = 100000000000;
      safeTx.targetTxGas = gasEstimate1.toNumber();
      safeTx.baseGas = 21000 + 21000; // base

      console.log(`Gas estimate1 ${gasEstimate1.toNumber()}`);
      console.log(`Gas cost estimate1 ${ethers.utils.formatUnits(safeTx.gasPrice * (safeTx.targetTxGas + safeTx.baseGas),"ether")}`); // Gas cost estimate should be about 0.006 ETH

      let safeSignData;
      safeSignData = await safeSignTypedData(accounts[0], ownerSCW, safeTx, chainId);
Frontrunning `deployCounterFactualWallet`
Audit: 2023-01-biconomy-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccountFactory.sol#L26-L39


# Vulnerability details

`_entryPoint` and `_handler` are not included in the salt, so a front-running attack could be possible for the same `_owner` and `_index` (same counterfactual address) with malicious `_entryPoint` and `_handler` (DoS/freeze funds) only entryPoint can be updated, not handler. Add to salt and below for `getAddressForCounterfactual`.
Frontrunning of smart wallet deployment
Audit: 2023-01-biconomy-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccountFactory.sol#L33-L75


# Vulnerability details

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

An attacker could obtain information about the `_owner` and  '_index'  parameters to front-run the deployment of a smart wallet.

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

Attack Exploit could go this way

contract Attacker {
WalletFactory walletFactory;

constructor(address walletFactoryAddress) public {
walletFactory = WalletFactory(walletFactoryAddress);
}

function attack(address _owner, address _entryPoint, address _handler, uint _index) public {

// Generate the address of the smart wallet that is going to be deployed
address walletAddress = walletFactory.getAddressForCounterfactualWallet(_owner, _index);

// Deploy a new smart wallet with the same _owner and _index parameters, but with a different _handler parameter
walletFactory.deployCounterFactualWallet(_owner, _entryPoint, address(this), _index);

// Check if the smart wallet was successfully deployed
require(walletFactory.isWalletExist(walletAddress), "Smart wallet was not deployed");
}
}

## Tools Used
Manual review

## Recommended Mitigation Steps

This risk could be avoided by ensuring the details about the  _owner, _entryPoint, and _handler parameters are kept confidential  when deploying a new smart wallet.
Failed transactions may lead to fund loss via replay attacks
Audit: 2023-01-biconomy-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L192-L245


# Vulnerability details

## Impact
Failed transactions inside ```executeTransaction()``` will revert and make the nonces[] remain intact. Thus a replay attack with signature arguments is possible.

## Proof of Concept
One can make replay attacks with the reverted ```executeTransaction()``` function and reproduce this transaction with identical signatures, leading to unexpected fund loss.
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L216

## Tools Used
Manual Review

## Recommended Mitigation Steps
The failed transactions should also increase the nonce.
The isContract function in LibAddress that uses EXTCODESIZE can be vulnerable to the "Contract Creation Code Execution" attack
Audit: 2023-01-biconomy-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L121


# Vulnerability details

## Impact
It will allow the attacker to potentially execute malicious code in the _implementation contract at https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L121

## Proof of Concept
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L121
```
contract Victim {


    function isContract(address account) public view returns(bool){
      uint32 csize;

      assembly {
        size := extcodesize(account)
      }
      return csize != 0;
    }

contract Attacker {
    
    bool public iTrickedIt;
    Victim v;
    
    constructor(address _v) public {
        v = Victim(_v);
        // addrss(this) doesn't have code, yet
        iTrickedIt = !v.isContract();
    }

}```

-Deploy Victim
- Deploy Attacker with Victim address
- Check iTrickedIt in Attacker


## Tools Used
Remix

## Recommended Mitigation Steps
Consider using another method than the "isContract()" function from LibAddress or the "isContract()" function from Openzeppelin's library, as both can be vulnerable when checking if the caller is a contract.
Front-running of the relayers transaction
Audit: 2023-01-biconomy-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/5df2e8f8c0fd3393b9ecdad9ef356955f07fbbdd/scw-contracts/contracts/smart-contract-wallet/aa-4337/core/EntryPoint.sol#L68


# Vulnerability details

## Description

While the relayer has a positive outcome from the execution of some of the user operations (through gas refund) it is profitable for the attacker to front-run such transactions with a full copy of those. In such a case attacker receives a positive outcome and in parallel does a griefing attack on the gas spent by the original relayer.

## Impact

Possibility of front-running the relayers transaction with incentives for the attacker. In parallel, it is a griefing attack on the gas spent by the relayer.

## Recommended Mitigation Steps

Add the possibility of signing the address of the relayer to have protection again such types of attacks. It is reasonable to maintain the possibility of an arbitrary relayer with `signedExecutor` equal to `address(0)`.
Destruction of the `SmartAccount` implementation
Audit: 2023-01-biconomy-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L166
https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L192
https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L229
https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/base/Executor.sol#L23


# Vulnerability details

## Description

If the `SmartAccount` implementation contract is not initialized, it can be destroyed using the following attack scenario:

- Initialize the `SmartAccount` **implementation** contract using the `init` function.
- Execute a transaction that contains a single `delegatecall` to a contract that executes the `selfdestruct` opcode on any incoming call, such as:

```solidity=
contract Destructor {
    fallback() external {
        selfdestruct(payable(0));
    }
}
```

The destruction of the implementation contract would result in the freezing of all functionality of the wallets that point to such an implementation. It would also be impossible to change the implementation address, as the `Singleton` functionality and the entire contract would be destroyed, leaving only the functionality from the Proxy contract accessible.

---

In the deploy script there is the following logic:

```typescript
const SmartWallet = await ethers.getContractFactory("SmartAccount");
const baseImpl = await SmartWallet.deploy();
await baseImpl.deployed();
console.log("base wallet impl deployed at: ", baseImpl.address);
```

So, in the deploy script there is no enforce that the `SmartAccount` contract implementation was initialized.

The same situation in `scw-contracts/scripts/wallet-factory.deploy.ts` script.

---

Please note, that in case only the possibility of initialization of the `SmartAccount` implementation will be banned it will be possible to use this attack. This is so because in such a case `owner` variable will be equal to zero and it will be easy to pass a check inside of `checkSignatures` function using the fact that for incorrect input parameters `ecrecover` returns a zero address.

## Impact

Complete freezing of all functionality of all wallets (including complete funds freezing).

## Recommended Mitigation Steps

Add to the deploy script initialization of the `SmartAccount` implementation, or add to the `SmartAccount` contract the following constructor that will prevent implementation contract from the initialization:

```solidity=
// Constructor ensures that this implementation contract can not be initialized
constructor() public {
    owner = address(1);
}
```
Unsigned `tokenGasPriceFactor` parameter
Audit: 2023-01-biconomy-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/5df2e8f8c0fd3393b9ecdad9ef356955f07fbbdd/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L239
https://github.com/code-423n4/2023-01-biconomy/blob/5df2e8f8c0fd3393b9ecdad9ef356955f07fbbdd/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L264
https://github.com/code-423n4/2023-01-biconomy/blob/5df2e8f8c0fd3393b9ecdad9ef356955f07fbbdd/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L424


# Vulnerability details

## Description

For the calculation of the amount of the token to be paid to the relayer `tokenGasPriceFactor` value is used. The corresponding logic is the following:

```solidity=
payment = (gasUsed + baseGas) * (gasPrice) / (tokenGasPriceFactor);
require(transferToken(gasToken, receiver, payment), "BSA012");
```

So, the number of tokens that the relayer should receive is inversely proportional to the value of this variable. But the `tokenGasPriceFactor` parameter is **not** signed by the owner:

```solidity=
bytes32 safeTxHash =
    keccak256(
        abi.encode(
            ACCOUNT_TX_TYPEHASH,
            _tx.to,
            _tx.value,
            keccak256(_tx.data),
            _tx.operation,
            _tx.targetTxGas,
            refundInfo.baseGas,
            refundInfo.gasPrice,
            refundInfo.gasToken,
            refundInfo.refundReceiver,
            _nonce
        )
    );
```

Using this fact the relayer can pass any value of `tokenGasPriceFactor` parameter to receive a greater amount than the user expected to pay.

### Attack scenario

The user sends a transaction to the relayer. By the formula, `payment` value should be equal `gasUsed/tokenGasPriceFactor`. The user expects that the `tokenGasPriceFactor` will be equal to `100` for the specified token, but the relayer put `tokenGasPriceFactor` to `1`. Relayer receives `100` times greater payout.

## Impact

Obtaining a greater relayer benefit than the user expects, theft of user funds.

## Recommended Mitigation Steps

Add the `refundInfo.tokenGasPriceFactor` to the preimage of the tx hash which should be signed by the owner.