Frankencoin - decade'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: 71/199

Findings: 3

Award: $43.73

QA:
grade-b
Gas:
grade-b

🌟 Selected for report: 1

🚀 Solo Findings: 0

Lines of code

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

Vulnerability details

Impact

Incorrect typo in function restructureCapTable() leading to only burning tokens of first address of addressToWipe array arguement.

Proof of Concept

Here, in L313, addressToWipe[0] only takes first address of the array. While ignoring the rest and also since first address's tokens are burned it will fail addressesToWipe array has more than one addresses.

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 Review

Change address current = addressesToWipe[0]; ==> address current = addressesToWipe[i];

#0 - c4-pre-sort

2023-04-20T14:11:32Z

0xA5DF marked the issue as primary issue

#1 - c4-sponsor

2023-04-29T23:16:20Z

luziusmeisser marked the issue as sponsor confirmed

#2 - c4-judge

2023-05-04T05:56:27Z

hansfriese marked the issue as satisfactory

#3 - c4-judge

2023-05-18T17:00:20Z

hansfriese marked the issue as selected for report

1. Meta Transactions not supported due to removal of Context.


2. Add a MAX application_fee check in suggestMinter()

_transfer(msg.sender, address(reserve), _applicationFee);

3. Check for _minApplicationPeriod to be greater than zero in constructor.

constructor(uint256 _minApplicationPeriod) ERC20(18){ MIN_APPLICATION_PERIOD = _minApplicationPeriod; reserve = new Equity(this); }

Source : https://github.com/Uniswap/v3-core/blob/main/audits/tob/audit.pdf

4. No ability to update reserve in Frankencoin.sol in an unlikely scenario.

5. Consider Two-step ownership transfer for all Position contracts.

  • Two-step ownership transfer prevents of falling of ownership of Position contract to unintended address which can be either malicious, inactive or contract address that are unable to any transaction.
  • Currently, Ownable contracts directly transfers ownership of the contract in a single transaction.
  • https://github.com/solodit/solodit_discussion/discussions/8403

#0 - 0xA5DF

2023-04-27T10:28:59Z

5 is in automated findings

#1 - c4-judge

2023-05-16T16:07:44Z

hansfriese marked the issue as grade-b

Gas Optimizations

1. totalSupply() unnecessarily checked twice.

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

function suggestMinter(address _minter, uint256 _applicationPeriod, uint256 _applicationFee, string calldata _message) override external { if (_applicationPeriod < MIN_APPLICATION_PERIOD && totalSupply() > 0) revert PeriodTooShort(); if (_applicationFee < MIN_FEE && totalSupply() > 0) revert FeeTooLow();
  • In a if statement with && operand, if the first check is fails, second is check is not reached and if statement returns false. Hence, putting totalSupply()>0 check first will not require you to put it twice.

2. _minCollateral require check in function openPosition() can be shifted above so that users have to less fee on transaction failure.

require(_initialCollateral >= _minCollateral, "must start with min col");
  • If this require statement fails, then transaction fails. But user still have to pay the gas used by the transaction upto that require statement.
  • Now, since the position contract is deployed, this gas used fee also increases multifold since the transaction inevitably fails.
  • If this require check is placed before the position contract is created, then user would not have to pay that much amount during transaction.

3. No need wrapping createClone() return value inside Position interface in clonePosition() function.

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

function clonePosition(address _existing) external returns (address) { Position existing = Position(_existing); Position clone = Position(createClone(existing.original())); return address(clone); }
  • createClone() returns address itself. There is not need for wrapping it under Position interface and then return address(clone). You can directly return the address.

#0 - c4-judge

2023-05-16T13:55:53Z

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