xanoutas/solidity-security-findings huggingface dataset:
Use of revert Statement in requireOwner Function in Ownable Contract May Cause Unexpected Behavior
Audit: 2023-04-frankencoin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details


Summary:
 There is a potential issue with the error handling in the requireOwner function that may lead to unexpected behavior.

Description:
Description:
The Ownable contract provides basic access control by defining an owner address that can be granted exclusive access to specific functions. The requireOwner function checks if the sender of the transaction is the owner of the contract, and if not, it throws an exception using the revert statement. This may cause unexpected behavior if the current transaction is discarded, potentially resulting in loss of funds or other unintended consequences for users of the contract.

Impacts:
The use of revert statement in requireOwner function may have a moderate to high impact depending on the context in which the contract is used. If the requireOwner function is called and the sender is not the owner, it will throw an exception using the revert statement. This may cause the current transaction to fail and any changes made in the current transaction to be discarded, which could result in loss of funds or other unintended consequences for users of the contract. To avoid these issues, it is recommended to replace the revert statement with the require statement in the requireOwner function and include a custom error message to provide more information to the user about why the function failed.



Severity:
This issue may have a moderate to high severity depending on the context in which the contract is used. If the requireOwner function is called and the sender is not the owner, it will throw an exception using the revert statement. This may cause the current transaction to fail and any changes made in the current transaction to be discarded, which could result in loss of funds or other unintended consequences for users of the contract.

Steps to Reproduce:

Deploy the contract on the Ethereum blockchain.
Call a function that requires the sender to be the owner of the contract, such as transferOwnership.
Call the function with an address that is not the current owner.
Observe that the transaction fails and any changes made in the current transaction are discarded.
Expected Behavior:
When the requireOwner function is called, it should check if the sender of the transaction is the owner of the contract. If the sender is not the owner, it should throw an exception using the require statement to prevent the function from executing and return an error message to the user.

Actual Behavior:
The requireOwner function uses the revert statement to throw an exception if the sender is not the owner. The revert statement will cause the current transaction to fail and any changes made in the current transaction to be discarded.

Recommendation:
To avoid unexpected behavior and potential loss of funds or other unintended consequences, it is recommended to replace the revert statement with the r
Minters can burn from other minters
Audit: 2023-04-frankencoin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
There's no control access on the `burnFrom` function, allowing any minter to burn tokens fro any other minter. This breaks an implied invariant that token holders are the one controlling their tokens.

## Recommended Mitigation Steps
Either check the alloance to make sure only authorized parties can burn or restrict to the msg.sender shares.
Position owners may get an unfair profits.
Audit: 2023-04-frankencoin-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L252-L276


# Vulnerability details

## Impact
Successfully completed challenge means that the owner of the position has lost the challenge.
But in this codebase, the owner of position will keep their ZCHFs because there is no any transfer or burn after finishing a challenge successfully
If the owner mint all ZCHFs against their entire collaterals, the owner of the position will get an unfair profits.
## Proof of Concept
Here is the test codebase:
```
import {expect} from "chai";
import { floatToDec18, dec18ToFloat } from "../scripts/math";
const { ethers, network } = require("hardhat");
const BN = ethers.BigNumber;
import { createContract } from "../scripts/utils";

let ZCHFContract, mintingHubContract, accounts;
let positionFactoryContract;
let mockXCHF, mockVOL, bridge;
let owner, sygnum, challenger, bidder;
describe("Position Tests", () => {
    before(async () => {
        accounts = await ethers.getSigners();
        owner = accounts[0].address;
        sygnum = accounts[1].address;
        challenger = accounts[2].address;
        bidder = accounts[3].address;
        // create contracts
        ZCHFContract = await createContract("Frankencoin", [10 * 86_400]);
        positionFactoryContract = await createContract("PositionFactory");
        mintingHubContract = await createContract("MintingHub", [ZCHFContract.address, positionFactoryContract.address]);
        // mocktoken
        mockXCHF = await createContract("TestToken", ["CryptoFranc", "XCHF", 18]);
        // mocktoken bridge to bootstrap
        let limit = floatToDec18(1000_000);
        bridge = await createContract("StablecoinBridge", [mockXCHF.address, ZCHFContract.address, limit]);
        ZCHFContract.suggestMinter(bridge.address, 0, 0, "XCHF Bridge");
        // create a minting hub too while we have no ZCHF supply
        ZCHFContract.suggestMinter(mintingHubContract.address, 0, 0, "Minting Hub");
        // wait for 1 block
        await ethers.provider.send('evm_increaseTime', [60]); 
        await network.provider.send("evm_mine");
        // now we are ready to bootstrap ZCHF with Mock-XCHF
        await mockXCHF.mint(owner, limit.div(2));
        await mockXCHF.mint(sygnum, limit.div(2));
        await mockXCHF.mint(bidder, limit.div(2));
        let balance = await mockXCHF.balanceOf(sygnum);
        expect(balance).to.be.equal(limit.div(2));
        // mint some ZCHF to block bridges without veto
        let amount = floatToDec18(20_000);
        await mockXCHF.connect(accounts[1]).approve(bridge.address, amount);
        await bridge.connect(accounts[1])["mint(uint256)"](amount);
        // owner mints some to be able to create a position
        await mockXCHF.connect(accounts[0]).approve(bridge.address, amount);
        await bridge.connect(accounts[0])["mint(uint256)"](amount);
        await mockXCHF.connect(accounts[3]).approve(bridge.address, amount);
0 value bids can be used to drain reserves
Audit: 2023-04-frankencoin-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/MintingHub.sol#L199-L229


# Vulnerability details

## Impact
The bidding mechanism is crucial to allow proper liquidations and hold proper prices. This functionality also allows the reserve to take a loss in certain cases, to ensure that challengers and bidders are well incentivized. This however can also be used to drain the reserves of the protocol.

The `bid()` function does not have enough checks to prevent an exploit. Firstly, it accepts bids with zero values. This can be mitigated easily with a check but doesn't solve the problem in its entirety, and will be used as a demonstrator case to run the exploit.

Let's assume there exists an empty position, with collateral = 0, and minted amount = 0. This can be the state of a position after a complete liquidation. The conditions need not be exactly zero for the exploit, but lets assume so for simplicity.

1. User Bob creates a challenge on this empty position with a non-zero `_collateralAmount`. `_collateralAmount` is an argument passed during the creation of a challenge denoting the amount of underlying to challenge. Even though the position is empty, we challenge a non-zero amount which we will fund the position with before the last step of the exploit.

2. The challenge is created and is open for bidding. User Bob is charged the amount `_collateralAmount` as the challenger's collateral which will be returned during the liquidation process.

3. User Bob creates a bid with zero `_bidAmountZCHF`. It doesn't have to be zero, but we will use that amount for simplicity. There are no restrictions in the contract for this, and the bid is successful. Since this is an empty position, no other liquidators would be interested in touching this position since there is no collateral to collect.

4. After waiting the challenge period, user Bob calls `end()` on the challenge. Before calling `end()`, user bob sends out the `_collateralAmount` amount of collateral tokens to the position with a normal transfer. Both these calls can be bundled in the same transaction by calling through a contract to prevent any external interference in between.
The function `end` calls on the Position contract to supply it with some important values.
```solidity
(
            address owner,
            uint256 effectiveBid,
            uint256 volume,
            uint256 repayment,
            uint32 reservePPM
        ) = challenge.position.notifyChallengeSucceeded(
                recipient,
                challenge.bid,
                challenge.size
            );
```
In the function `notifyChallengeSucceeded`, these values are calculated as shown.
```solidity
        challengedAmount -= _size;
        uint256 colBal = collateralBalance();
        if (_size > colBal) {
            _bid = _divD18(_mulD18(_bid, colBal), _size);
            _size = colBal;
        }
        uint256 volumeZCHF = _mulD18(pric
Code and design allow position owners to steal challenger collateral for a fraction of their price
Audit: 2023-04-frankencoin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The current implementation allows a Position owner to potentially "rekt" any challenger via a simple sandwich attack. 
In the typical happy flow a challenger calls the `launchChallenge` function and provides a certain amount of collateral. Once a challenge is launched and collateral is moved from challenger into the minting hub contract, most aspects of the Position contract become immutable. At this point different actors can post bids to "buy off" the collateral using their ZCHF tokens given the price set in the position. If a bidder buys the collateral with the proper price then the challenge is "averted". 
The current system design allows the owner of the position to decrease the price in ZCHF of the collateral. Increasing the price incurs a cooldown period since that would allow them to mint more ZCHF tokens, but this doesn't happen when decreasing the price.
Thus when the owner of a position discovers a `launchChallenge` TX in the mempool against their position they are incentivized to sandwich the challenger with the following bundle:
    1. Owner of position sends an `adjustPrice` TX cutting the price in half (or even worse)
    2. Challenger's original `launchChallenge` TX
    3. Owner of position sends a `bid` TX with half the amount of the ZCHF tokens that the challenger would have expected, averting the challenge and collecting the challenger's collateral for half the price.

The position owner is able to steal a significant portion of a challenger's collateral.

## Proof of Concept
To avert a challenge a bidder must send an amount of ZCHF tokens (_bidAmountZCHF) such that: 
`_bidAmountZCHF * ONE_DEC18 >= price * _collateralAmount` where `_collateralAmount` is the size of the challenge according to this line: https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Position.sol#L307
Also notice that to decrease the price of the collateral, no cooldown is required: https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Position.sol#L159

It is simple to simulate the sandwich attack and see its consequences with a simple manipulation of the PositionTests file and specifically the test in this line: https://github.com/code-423n4/2023-04-frankencoin/blob/main/test/PositionTests.ts#L204

Replace the code of this test with the following code:
```
it("send challenge", async () => {
            challengeAmount = initialCollateralClone/2;
            let fchallengeAmount = floatToDec18(challengeAmount);
            await mockVOL.connect(accounts[0]).approve(mintingHubContract.address, fchallengeAmount);
            let price = dec18ToFloat(await clonePositionContract.price());
            console.log('Original price: ', price)
            let divider = 2;
            console.log('-------------------------------')
            console.log('ZCHF Balance of challenger: ', dec18T
msg.sender should not get position owner's collateral if there are no bidders
Audit: 2023-04-frankencoin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Any party can get collateral without needing to participate in the protocol. The position owner will lose collateral unnecessarily.

## Proof of Concept

When the challenge ends and there are no bidders, the msg.sender becomes the recipient.

```
        address recipient = challenge.bidder == address(0x0) ? msg.sender : challenge.bidder;
```

Next, notifyChallengeSucceeded() is called, 

```
    function notifyChallengeSucceeded(address _bidder, uint256 _bid, uint256 _size) external onlyHub returns (address, uint256, uint256, uint256, uint32) {
        challengedAmount -= _size;
        uint256 colBal = collateralBalance();
        if (_size > colBal){
            // Challenge is larger than the position. This can for example happen if there are multiple concurrent
            // challenges that exceed the collateral balance in size. In this case, we need to redimension the bid and
            // tell the caller that a part of the bid needs to be returned to the bidder.
            _bid = _divD18(_mulD18(_bid, colBal), _size);
            _size = colBal;
        }


        // Note that thanks to the collateral invariant, we know that
        //    colBal * price >= minted * ONE_DEC18
        // and that therefore
        //    price >= minted / colbal * E18
        // such that
        //    volumeZCHF = price * size / E18 >= minted * size / colbal
        // So the owner cannot maliciously decrease the price to make volume fall below the proportionate repayment.
        uint256 volumeZCHF = _mulD18(price, _size); // How much could have minted with the challenged amount of the collateral
        // The owner does not have to repay (and burn) more than the owner actually minted.  
        uint256 repayment = minted < volumeZCHF ? minted : volumeZCHF; // how much must be burned to make things even


        notifyRepaidInternal(repayment); // we assume the caller takes care of the actual repayment
        internalWithdrawCollateral(_bidder, _size); // transfer collateral to the bidder and emit update
        return (owner, _bid, volumeZCHF, repayment, reserveContribution);
    }
```

internalWithdrawCollateral is called with the parameters (_bidder and _size), indicating the msg.sender and the colalteral amount. 

```
    function internalWithdrawCollateral(address target, uint256 amount) internal returns (uint256) {
        IERC20(collateral).transfer(target, amount);
        uint256 balance = collateralBalance();
        if (balance < minimumCollateral){
            cooldown = expiration;
        }
        emitUpdate();
        return balance;
    }
```

the collateral amount (_size) is then transferred to the msg.sender.

For example, 

1. The position owner, Alice, has 1000 ZCHF and 1 WETH. 
2. The challenger, Bob, challenges the position with 0.5 WETH. The size of
Users unfairly receiving unexpected proceeds when withdrawing ZCHF using `Equity::redeem()`
Audit: 2023-04-frankencoin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
After staking period of ZCHF finishes in `Equity`, holder can redeem their locked up funds. However, shares to proceeds calculations are incorrect, hence first depositors can redeem more than they put in, while late depositors will receive less than expected. The flow starts with this function:

```
    function redeem(address target, uint256 shares) public returns (uint256) {
        require(canRedeem(msg.sender));
        uint256 proceeds = calculateProceeds(shares);
        _burn(msg.sender, shares);
        zchf.transfer(target, proceeds);
        emit Trade(msg.sender, -int(shares), proceeds, price());
        return proceeds;
    }
```

The main part is calculations of proceeds. The amount of underlying ZCHF to receive is calculated using `calculateProceeds()` function, which is similar to `calculateSharesInternal()` and unfortunately, as described in my other submission, shares similar issue of wrong calculations:

```
    function calculateProceeds(uint256 shares) public view returns (uint256) {
        uint256 totalShares = totalSupply();
        uint256 capital = zchf.equity();
        require(shares + ONE_DEC18 < totalShares, "too many shares"); // make sure there is always at least one share
        uint256 newTotalShares = totalShares - shares;
        uint256 newCapital = _mulD18(capital, _power3(_divD18(newTotalShares, totalShares)));
        return capital - newCapital;
    }
```

This is crucial, as this formula calculates more proceeds than there is underlying value, awarding first depositors with unfair proceeds, while penalizing late depositors. Concrete example of it will be descirbeed in the next section.

## Proof of Concept
In the test case below I prepared 3 users - A, B and C  - who stake the same amount of ZCHF - 100_000e18. After a year they try to redeem their shares. Based on the calculations in `Equity::calculateProceeds()`, they should receive the following amount of ZCHF:
  Alice proceeds =  292450914645463056294000
  Bob proceeds =  134533839734974724800000
  Charlie proceeds =  99999998341144330856000
This means, that given that 300k ZCHF is in the pool:
* Alice can withdraw ~292450 ZCHF, which is almost 3x what she puts in
* Bob can withdraw ~134533 ZCHF, which is ~1.3x what he puts in
* Charlie can withdraw ~99999.99 ZCHF, which is expected given the small rounding down
Of course there is not enough ZCHF in the system, so neither Bob nor Charlie will be able to get their money back when Alice decides to withdraw everything, and she will be unfairly awarded almost 3x of what she put in, jsut because she was first one.

To check this for yourself, please put following test under `test/GeneralTest.t.sol` and run
`forge test -m 'RedeemMiscalculation'`
```solidity
    function test_R
Potential Risk of Over-Minting and Under-Collateralization in Position Cloning
Audit: 2023-04-frankencoin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
potential risk of over-minting and under-collateralization in position cloning can 
lead to a decrease in the value of the token and also loss of trust in the system by users.

## Proof of Concept
https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/Position.sol#L76-L101

The smart contract  includes  **initializeClone()** and **reduceLimitForClone()** functions. The initializeClone() function takes a limit as a parameter 
and the **reduceLimitForClone()** function reduces the limit of the previous clone by half.
From the developers comments  the burden of remembering to call **reduceLimitForClone** before calling initializeClone falls on the creator and as usual human error is always a possibility. Reliance on human memory and attention to detail can easily lead to errors.
```
/**
     * Method to initialize a freshly created clone. It is the responsibility of the creator to make sure this is only
     * called once and to call reduceLimitForClone on the original position before initializing the clone.
     */
```
https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/Position.sol#L72-L75

Imagine of a situation where the creator does not follow the required order of calling these functions due to a
human error or possibly being unaware of the potential risk.
for instance, if a creator  interacts with the smart contract and calls the **initializeclone()** function before calling **reducelimitforclone()**. The function will still work perfectly fine and the contract will create a new clone with the specified limit.
However, since the user did not call **reducelimitforclone()** before calling **initializeclone()**, the limit for the new clone has not been reduced. This means that the new clone has the same limit as the previous clone. This implies that the system can allow for the creation of a new clone without reducing its limit. In proper design flows,a system should not allow this. The total supply of the token can eventually exceed the amount of collateral backing increasing the risk of over-minting and can cause issues with the system's collateralization.

## Tools Used
Manual analysis

## Recommended Mitigation Steps
The code should be designed in such a way that the sequence of actions is enforced by the system itself. For example the **initializeClone** function could check whether **reduceLimitForClone** has been called and, if not, 
throw an error prompting the creator to call it first.
I recommend the following:
Introduction of a boolean flag to keep track of whether the limit has been reduced before creating a new clone.
The flag is set to true after the limit has been reduced and set to false after a new clone has been initialized.
In the initializeClon