Frankencoin - Arz's results

A decentralized and fully collateralized stablecoin.

General Information

Platform: Code4rena

Start Date: 12/04/2023

Pot Size: $60,500 USDC

Total HM: 21

Participants: 199

Period: 7 days

Judge: hansfriese

Total Solo HM: 5

Id: 231

League: ETH

Frankencoin

Findings Distribution

Researcher Performance

Rank: 105/199

Findings: 2

Award: $22.67

QA:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

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

Vulnerability details

Impact

Brief/Intro

The restructureCapTable() has a loop issue that only burns tokens from the first address in the array. Because of this the cap table isn't restructured as intended.

Explanation

restructureCapTable() is a function in Equity.sol that can be called when the system is at risk and qualified FPS holders want to restructure the system. This function burns FPS tokens from the qualified FPS holders that want to restructure the system. However because the current address in the loop is always set to the first address in the array, this only burns FPS tokens from the first address in the array instead of every single address in the array. If there is a devastating loss in the system, this function doesnt help resolve the issue

Impact

The impact of this issue is that the restructureCapTable() function is not functioning as intended. Since the loop always sets the current address to the first address in the array, the function only burns FPS tokens from the first address in the array, while leaving the rest of the addresses untouched. This means that the function is not restructuring the cap table as it should, and the system remains at risk.

Code

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)); } }

Set address current = addressesToWipe[i] so that it burns tokens from all of the addresses

#0 - c4-pre-sort

2023-04-20T14:15:43Z

0xA5DF marked the issue as duplicate of #941

#1 - c4-judge

2023-05-18T14:22:36Z

hansfriese marked the issue as satisfactory

Title

Incorrect amount of shares can be minted because the cubicRoot function returns incorrect results if the given number is not a 1e+18

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

Brief/Introduction

The amount of shares can be calculated incorrectly if the given number is not a 1e+18 for the cubicRoot() function in Equity.sol. Users can get more than allowed shares.

Explanation

If the totalShares < 1000 * ONE_DEC18, the newTotalShares are calculated using this equation:

_mulD18(totalShares, _cubicRoot(_divD18(capitalBefore + investment, capitalBefore))

The problem is when _divD18() returns a 1e19(or bigger) number for the cubicRoot function.

The cubicRoot function expects a 1e18 and returns incorrect results if the number given is not an 1e18 number.

This leads to incorrect calculation of the shares that the user will get and could leave the user with more shares than allowed.

Impact

If _divD18() returns a number that is bigger(more zeros) than 1e18, the cubicRoot() function will receive an input that is not an 1e18 number, and will therefore return incorrect results

Tests

In the test i did what Equity.sol is doing: _divD18(capitalBefore + investment, capitalBefore)) but i set investment to be a high number so when you _divD18() these 2 you get a 1e19 number and that number is then passed to the cubicRoot() which expects a 1e18 number but because the number is a 1e19 it returns incorrect results

contract TestMathUtil is Test { MathUtil math; uint256 private constant MINIMUM_EQUITY = 1000 * ONE_DEC18; //Amount im transferring so i can make the divD18 return a 1e19 number uint256 private constant INVESTMENT = 9_000_000_000_000_000_000_000; function setUp() public { math = new MathUtil(); } uint256 internal constant ONE_DEC18 = 1018; function testDivD18() public view { //returns 1e19 uint256 incorrectResult = math._divD18(MINIMUM_EQUITY + INVESTMENT, MINIMUM_EQUITY); //_cubicRoot(uint256 _v) expects _v to be a 1e18 //this returns incorrect results console.log("THE BAD RESULT IS", math._cubicRoot(incorrectResult)); //returns 1e18 uint correctResult = math._divD18(MINIMUM_EQUITY, MINIMUM_EQUITY); //this returns the correct results console.log("THE CORRECT RESULT IS", math._cubicRoot(correctResult)); } }

Recommendation

Review the function for proper adjustments

#0 - c4-judge

2023-05-16T16:32:49Z

hansfriese marked the issue as grade-b

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter