xanoutas/solidity-security-findings huggingface dataset:
The function will not work as intended
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L309
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L313


# Vulnerability details

## Impact
The function takes  addresses list and perform burning operation `addressesToWipe.length` times. While iterating through the `addressesToWipe.length` within a for loop, it inadvertently burns tokens from the address at the 0 index during each iteration.

## Proof of Concept
1- Send three distinct valid `helpers` and `addressesToWipe` addresses as input.
2- Given that `addressesToWipe.length` is 3, the for loop will execute three times.
3- During the first execution of the `_burn` function, the entire balance of the address at the zero index is burned.
4- In the second execution of the `_burn` function, the balance of the address at the zero index is burned again. However, the balance is now zero, as it was already burned in the previous step.
5- Repeat step 4 for the third execution.
6- The function is successfully completed without reverting.

![ScreenShot](https://i.imgur.com/mCWpWjG.png)
## Tools Used
Manually
## Recommended Mitigation Steps
To ensure the intended behavior of burning tokens in order within the entire list of addresses, line 313 should be modified as follows:

`address current = addressesToWipe[i];`

This change will ensure that the function iterates through and burns the tokens for each address in the list.
Manipulation of total share amount might cause future depositors to lose their assets
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L266-L270


# Vulnerability details

## Bug Description

In the `Equity` contract, the `calculateSharesInternal()` function is used to determine the amount of shares minted whenever a user deposits Frankencoin:

[Equity.sol#L266-L270](https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L266-L270)

```solidity
function calculateSharesInternal(uint256 capitalBefore, uint256 investment) internal view returns (uint256) {
    uint256 totalShares = totalSupply();
    uint256 newTotalShares = totalShares < 1000 * ONE_DEC18 ? 1000 * ONE_DEC18 : _mulD18(totalShares, _cubicRoot(_divD18(capitalBefore + investment, capitalBefore)));
    return newTotalShares - totalShares;
}
```

Note that the return value is the amount of shares minted to the depositor. 

Whenever the total amount of shares is less than `1000e18`, the depositor will receive `1000e18 - totalShares` shares, regardless of how much Frankencoin he has deposited. This functionality exists to mint `1000e18` shares to the first depositor.

However, this is a vulnerability as the total amount of shares can decrease below `1000e18` due to the `redeem()` function, which burns shares: 

[Equity.sol#L275-L278](https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L275-L278)

```solidity
function redeem(address target, uint256 shares) public returns (uint256) {
    require(canRedeem(msg.sender));
    uint256 proceeds = calculateProceeds(shares);
    _burn(msg.sender, shares);
```

The following check in `calculateProceeds()` only ensures that `totalSupply()` is never below `1e18`:

[Equity.sol#L293](https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L293)

```solidity
require(shares + ONE_DEC18 < totalShares, "too many shares"); // make sure there is always at least one share
```

As such, if the total amount of shares decreases below `1000e18`, the next depositor will receive `1000e18 - totalShares` shares instead of an amount of shares proportional to the amount of Frankencoin deposited. This could result in a loss or unfair gain of Frankencoin for the depositor.

## Impact

If the total amount of shares ever drops below `1000e18`, the next depositor will receive a disproportionate amount of shares, resulting in an unfair gain or loss of Frankencoin.

Moreover, by repeatedly redeeming shares, an attacker can force the total share amount remain below `1000e18`, causing all future depositors to lose most of their deposited Frankencoin.  

## Proof of Concept

Consider the following scenario:
- Alice deposits 1000 Frankencoin (`amount = 1000e18`), gaining `1000e18` shares in return.
- After 90 days, Alice is able to redeem her shares. 
- Alice calls `redeem()` with `shares = 1` to redeem 1 share:
  - The total amount of shares is now `1000e18 - 1`.
- Bob deposits 1000 Frankencoin (`amount = 1000e18`). In `calculateSharesInte
anchorTime() will not work properly on Optimism due to use of block.number
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

 https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L173


# Vulnerability details

When deploying to Optimism, Equity.anchorTime() will not be accurate due to the use of block.number. 

```
    function anchorTime() internal view returns (uint64){
        return uint64(block.number << BLOCK_TIME_RESOLUTION_BITS);
    }
```

## Impact
The inaccuracy of block.number will affect the computation of the holding duration for the votes. That will affect redeem() as the issue will cause it to deviate from the intended design of 90 days minimum holding duration (stated in comments).

https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L54-L59

## Detailed Explanation
Noted that the devs have mentioned that it is conceivable that Frankencoin will be deployed on other evm chains. So it is worth reviewing the use of block.number, such that it is compatible with other chains like Optimism.

On Optimism, the `block.number` is not a reliable source of timing information and the time between each block is also different from Ethereum. This is because each transaction on L2 is placed in a separate block and blocks are not produce at a constant rate. This will cause the holding duration computation using `anchorTime()` to fluctuate. (see Optimism docs https://community.optimism.io/docs/developers/build/differences/#block-numbers-and-timestamps)



## Recommended Mitigation Steps
Consider using block.timestamp instead of block.number for more accurate measurement of time.
restructureCapTable function burns only first element of helpers array 
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L313


# Vulnerability details

## Impact
`restructureCapTable` function in Equity.sol burns only first element of helpers array 
## Proof of Concept
in below code snipet `address current = addressesToWipe[0];` makes this code only burn the balance of first element of helpers.
```solidity
  function restructureCapTable(address[] calldata helpers, address[] calldata addressesToWipe) public {
        require(zchf.equity() < MINIMUM_EQUITY);
        checkQualified(msg.sender, helpers);
        for (uint256 i = 0; i<addressesToWipe.length; i++){
            address current = addressesToWipe[0];//@audit
            _burn(current, balanceOf(current));
        }
    }
```
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L313

## Tools Used
Manual review
## Recommended Mitigation Steps
replace `0` with `i` so it burns balance of all helpers
```git

  function restructureCapTable(address[] calldata helpers, address[] calldata addressesToWipe) public {
        require(zchf.equity() < MINIMUM_EQUITY);
        checkQualified(msg.sender, helpers);
        for (uint256 i = 0; i<addressesToWipe.length; i++){
--            address current = addressesToWipe[0];//@audit
++            address current = addressesToWipe[i];

            _burn(current, balanceOf(current));
        }
    }
```
The contract can not restructure the cap table due to the iteration is wrong.
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/Equity.sol#L309-L316


# Vulnerability details

## Impact
Due to the wrong iteration code, the contract can not restructure the system.

## Proof of Concept
In this line of the contract https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/Equity.sol#L313

```solidity
    address current = addressesToWipe[0];
```

the code won't work as intended due to code is getting addressesToWipe variable's first index in every iteration.
```solidity
    function restructureCapTable(address[] calldata helpers, address[] calldata addressesToWipe) public {
        require(zchf.equity() < MINIMUM_EQUITY);
        checkQualified(msg.sender, helpers);
        for (uint256 i = 0; i<addressesToWipe.length; i++){
            address current = addressesToWipe[0];
            _burn(current, balanceOf(current));
        }
    }
```


## Tools Used
Manual

## Recommended Mitigation Steps
The correct code for this line: https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/Equity.sol#L313

Would be:

```solidity
    address current = addressesToWipe[i];
```


```solidity
    function restructureCapTable(address[] calldata helpers, address[] calldata addressesToWipe) public {
        require(zchf.equity() < MINIMUM_EQUITY);
        checkQualified(msg.sender, helpers);
        for (uint256 i = 0; i<addressesToWipe.length; i++){
            address current = addressesToWipe[i];
            _burn(current, balanceOf(current));
        }
    }
```
PositionFactory.sol : createNewPosition can be called by anyone to create position other than the hub
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/PositionFactory.sol#L13-L20


# Vulnerability details

## Impact
Malicious user can create a new position and pretend the pool as Frakencoin and deceive user to deposit in it.

## Proof of Concept

`createNewPosition` can be called by any user. This function does not have any access modifier.
While the comments says `Must be called through minting hub to be recognized as valid position`, but there are such restriction to call the function.

    /**
     * Create a completely new position in a newly deployed contract.
     * Must be called through minting hub to be recognized as valid position.
     */
    function createNewPosition(address _owner, address _zchf, address _collateral, uint256 _minCollateral, 
        uint256 _initialLimit, uint256 initPeriod, uint256 _duration, uint256 _challengePeriod, 
        uint32 _mintingFeePPM, uint256 _liqPrice, uint32 _reserve) 
        external returns (address) {
        return address(new Position(_owner, msg.sender, _zchf, _collateral, 
            _minCollateral, _initialLimit, initPeriod, _duration, 
            _challengePeriod, _mintingFeePPM, _liqPrice, _reserve));
    }

When we see the new position creation flow, `createNewPosition` is called from `MintingHub.sol#L88-L113`, minting hub contract is the caller here.
https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/MintingHub.sol#L88-L113

In `createNewPosition` function , for Position, it has the msg.sender as one of the argument. This msg.sender is mintinghub contract here.

           return address(new Position(_owner, msg.sender, _zchf, _collateral, 

When looking at the Position contract's constructor,

    constructor(address _owner, address _hub, address _zchf, address _collateral, 
        uint256 _minCollateral, uint256 _initialLimit, uint256 initPeriod, uint256 _duration,
        uint256 _challengePeriod, uint32 _mintingFeePPM, uint256 _liqPrice, uint32 _reservePPM) {
        require(initPeriod >= 3 days); // must be at least three days, recommended to use higher values
        setOwner(_owner);
        original = address(this);
        hub = _hub;
        price = _liqPrice;
        zchf = IFrankencoin(_zchf);
        collateral = IERC20(_collateral);
        mintingFeePPM = _mintingFeePPM;
        reserveContribution = _reservePPM;
        minimumCollateral = _minCollateral;
        challengePeriod = _challengePeriod;
        start = block.timestamp + initPeriod; // one week time to deny the position
        cooldown = start;
        expiration = start + _duration;
        limit = _initialLimit;
        
        emit PositionOpened(_owner, original, _zchf, address(collateral), _liqPrice);
    }

the second argument is hub. This mean, Minting hub is only allowed to create new position.

But when we look at the `createNewPosition` function inside the `PositionFa
Position contract will not work well if collateral is fees on transfer token
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Position.sol#L228


# Vulnerability details

## Impact

Contract Insolvency.

## Proof of Concept

Certain tokens, such as USDT, have the [ability to charge fees](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#code) on transfers. Although the fee rate is currently 0, this may change in the future. The result of this is that if `x` tokens are transferred, an amount less than `x` will be received. For any functions which utilize `transferFrom`, the expectation that the amount received will match the amount transferred cannot be relied upon.

If any position is deployed with the collateral as let's say USDT, then it can lead to issues in future with respect to handling of transfer amount as it is expected that whole amount is getting transferred in the contract.

## Tools Used

VS Code

## Recommended Mitigation Steps

Check the balance before and after the transfer to calculate the exact amount transferred.
Error in `restructureCapTable` function leads to burning only first address
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L309-L316


# Vulnerability details

## Impact

Due to an error in the `restructureCapTable` function only the balance of the first account in the `addressesToWipe` is burned, so the function will not accomplish the role it was intended to and the active FPS holders who participated in the restructure will be forced to share reserve with the passive FPS holders.

## Proof of Concept

The issue occurs in the `restructureCapTable` function :

File: Equity.sol [Line 309-316](https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L309-L316)
```solidity
function restructureCapTable(address[] calldata helpers, address[] calldata addressesToWipe) public {
    require(zchf.equity() < MINIMUM_EQUITY);
    checkQualified(msg.sender, helpers);
    for (uint256 i = 0; i<addressesToWipe.length; i++){
        /** @audit
            only first index is used so only first address will be burned
            It should be addressesToWipe[i] instead of addressesToWipe[0]
        */
        address current = addressesToWipe[0];
        _burn(current, balanceOf(current));
    }
}
```

The function above is supposed to burn the balances of all the accounts associated with the addresses in the `addressesToWipe` array, but because only the first index is taken in the for loop `addressesToWipe[0]` only the first address will get its balance burned and all others addresses balances will remain the same.

This is an intended behaviour as the goal of the function is to remove the passive FPS holders and to give all the shares to FPS holders who participated in the restructure. 

## Tools Used
Manual review

## Recommended Mitigation Steps

The correct index should be used in the `restructureCapTable` for loop, the function should modified as follows :

```solidity
function restructureCapTable(address[] calldata helpers, address[] calldata addressesToWipe) public {
    require(zchf.equity() < MINIMUM_EQUITY);
    checkQualified(msg.sender, helpers);
    for (uint256 i = 0; i<addressesToWipe.length; i++){
        /** @audit
            Use addressesToWipe[i] instead of addressesToWipe[0]
        */
        address current = addressesToWipe[i];
        _burn(current, balanceOf(current));
    }
}
```
Calling `Equity.restructureCapTable` function does not wipe all passive FPS holders of `addressesToWipe`
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L309-L316


# Vulnerability details

## Impact
When there is less than 1000 ZCHF in equity left, a donor can provide more ZCHF to save the Frankencoin system. Before donating to the reserve, this donor would call the following `Equity.restructureCapTable` function to wipe the passive FPS holders so she or he would not share the donation with these passive FPS holders. However, the `Equity.restructureCapTable` function executes `address current = addressesToWipe[0]` when iterating over each address of `addressesToWipe`. This means that only the first address of `addressesToWipe` is wiped. After calling this function, the donor would think that she or he has successfully wiped the passive FPS holders of `addressesToWipe` so she or he then donates to the reserve. Yet, since only the first passive FPS holder of `addressesToWipe` is wiped, the donor would unknowingly share the donation with the unwiped passive FPS holders. As a result, the donor does not own the 100% of all FPS shares when she or he should and lose parts of the donation to the passive FPS holders who should be wiped but not.

https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L309-L316
```solidity
    function restructureCapTable(address[] calldata helpers, address[] calldata addressesToWipe) public {
        require(zchf.equity() < MINIMUM_EQUITY);
        checkQualified(msg.sender, helpers);
        for (uint256 i = 0; i<addressesToWipe.length; i++){
            address current = addressesToWipe[0];
            _burn(current, balanceOf(current));
        }
    }
```

## Proof of Concept
First, please add the following code in `contracts\test\MintingHubTest.sol`.
1. Add the following `redeem` function in the `User` contract.
```solidity
    function redeem(address target, uint256 shares) public returns (uint256) {
        return Equity(address(zchf.reserve())).redeem(target, shares);
    }
```

2. Replace https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/test/MintingHubTest.sol#L25-L52 with the following code.
```solidity
    User alice;
    User bob;

    // @audit add following code for POC purpose
    User charlie;
    /** */

    address latestPosition;
    uint256 latestChallenge;

    constructor(address hub_, address swap_){
        hub = MintingHub(hub_);
        swap = StablecoinBridge(swap_);
        col = new TestToken("Some Collateral", "COL", uint8(0));
        xchf = swap.chf();
        zchf = swap.zchf();
        alice = new User(zchf);
        bob = new User(zchf);

        // @audit add following code for POC purpose
        charlie = new User(zchf);
        /** */
    }

    function initiateEquity() public {
        require(zchf.reserve().totalSupply() == 0, Strings.toString(zchf.reserve().totalSupply()));
        require(zchf.equity() == 1101000000000000000001, Strings.toString(zchf.equity()));
         // ensure there
[M-01] Incorrect array indexing in `Equity::restructureCapTable`
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/Equity.sol#L313


# Vulnerability details

## Impact

When `Equity::restructureCapTable` is called, the function loops through the shareholders provided and burns their shares. However, the array indexing is incorrect, meaning that the for `addressesToWipe` arrays with length > 1 the function will only burn the first shareholder's shares.

## Proof of Concept

N/A

##  Tools Used

- Manual review

## Recommended Mitigation

Correctly update the current address by indexing the array with the loop index instead of 0: `address current = addressesToWipe[i];`
`openPosition` is suspicious of the reorg attack
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L92-L106
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/PositionFactory.sol#L17


# Vulnerability details

## Impact

Exploiting involving Stealing of funds.

## Proof of Concept

The `openPosition` function deploys a new position contract using the create, where the address derivation depends only on the arguments passed.

At the same time, some of the chains (Polygon, Optimism, Arbitrum) to which the `MintingHub` will be deployed are suspicious of the reorg attack.

```solidity
File: PositionFactory

17:    return address(new Position(_owner, msg.sender, _zchf, _collateral, 
18:    _minCollateral, _initialLimit, initPeriod, _duration, 
19:    _challengePeriod, _mintingFeePPM, _liqPrice, _reserve));

```
[Link to code](https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/PositionFactory.sol#L17)

Even more, the reorg can be couple of minutes long. So, it is quite enough to create the position and transfer funds to that address, especially when someone uses a script, and not doing it by hand.

Optimistic rollups (Optimism/Arbitrum) are also suspect to reorgs since if someone finds a fraud the blocks will be reverted, even though the user receives a confirmation and already created a position.

If Alice creates a new position by deploying a position, and then sends funds to it. Bob sees that the network block reorg happens and calls openPosition. Thus, it creates position with an address to which Alice sends funds. Then Alices' transactions are executed and Alice transfers funds to Bob's controlled position.

## Tools Used

VS Code

## Recommended Mitigation Steps

Deploy the position contract via `create2` with `salt`.