Frankencoin - carrotsmuggler'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: 21/199

Findings: 3

Award: $414.80

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: carrotsmuggler

Also found by: Ace-30, KIntern_NA, Nyx, bin2chen, cccz, juancito, mahdikarimi, mov, nobody2018

Labels

bug
3 (High Risk)
primary issue
satisfactory
selected for report
sponsor confirmed
H-01

Awards

261.4589 USDC - $261.46

External Links

Lines of code

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

Vulnerability details

Impact

Challenges, once created, cannot be closed. Thus once a challenge is created, the challenger has already transferred in a collateral amount and is thus open for losing their collateral to a bidding war which will most likely close below market price, since otherwise buying from the market would be cheaper for bidders.

Position owners can take advantage of this fact and frontrun a launchChallenge transaction with an adjustPrice transaction. The adjustPrice function lets the user lower the price of the position, and can pass the collateral check by sending collateral tokens externally.

As a worst case scenario, consider a case where a position is open with 1 ETH collateral and 1500 ZCHF minted. A challenger challenges the position and the owner frontruns the challenger by sending the contract 1500 ZCHF and calling repay() and then calling adjustPrice with value 0, all in one transaction with a contract. Now, the price in the contract is set to 0, and the collateral check passes since the outstanding minted amount is 0. The challenger's transaction gets included next, and they are now bidding away their collateral, since any amount of bid will pass the avert collateral check.

The position owner themselves can backrun the same transaction with a bid of 1 wei and take all the challenger's collateral, since every bid checks for the tryAvertChallenge condition.

if (_bidAmountZCHF * ONE_DEC18 >= price * _collateralAmount)

Since price is set to 0, any bid passes this check. This sandwich attack causes immense losses to all challengers in the system, baiting them with bad positions and then sandwiching their challenges.

Since sandwich attacks are extremely commonplace, this is classified as high severity.

Proof of Concept

The attack can be performed the following steps.

  1. Have an undercollateralized position. This can be caused naturally due to market movements.
  2. Frontrun challenger's transaction with a repayment and adjustPrice call lowering the price.
  3. Challenger's call gets included, where they now put up collateral for bids.
  4. Backrun challenger's call with a bid such that it triggers the avert.
  5. Attacker just claimed the challenger's collateral at their specified bid price, which can be as little as 1 wei if price is 0.

Tools Used

Manual Review

When launching a challenge, ask for a expectedPrice argument. If the actual price does not match this expected price, that means that transaction was frontrun and should be reverted. This acts like a slippage check for challenges.

#0 - c4-pre-sort

2023-04-21T11:56:39Z

0xA5DF marked the issue as primary issue

#1 - 0xA5DF

2023-04-21T15:15:34Z

I have some doubts about severity, since the auction's final bid is expected to be at about the worth of the collateral. So the challenger isn't expected to lose anything but the challenge reward.

#2 - luziusmeisser

2023-04-29T23:15:48Z

This is actually a high risk issue as the challenge is ended early as soon as the highest bid reaches the liquidation price.

I would even say that this is one of the most valuable findings I've seen so far!

The fix is to add front-running protection to the launchChallenge function:

function launchChallenge(address _positionAddr, uint256 _collateralAmount, uint256 expectedPrice) external validPos(_positionAddr) returns (uint256) { IPosition position = IPosition(_positionAddr); if (position.price() != expectedPrice) revert UnexpectedPrice();

#3 - c4-sponsor

2023-04-29T23:15:58Z

luziusmeisser marked the issue as sponsor confirmed

#4 - hansfriese

2023-05-04T03:08:42Z

Since the owner lower the price of the position, the collateral for a challenge worth nothing, and the challengers might lose their collateral. So I am agree with the sponsor.

#5 - c4-judge

2023-05-04T03:08:57Z

hansfriese marked the issue as satisfactory

#6 - c4-judge

2023-05-18T15:13:00Z

hansfriese marked the issue as selected for report

Lines of code

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

Vulnerability details

Impact

The function restructureCapTable loops over a list of users and burns their holdings. However, it is incorrectly coded and only burns the first element.

for (uint256 i = 0; i<addressesToWipe.length; i++){
            address current = addressesToWipe[0]; //@audit should be i, not 0
            _burn(current, balanceOf(current));
        }

Since this breaks the purpose of the function, it is classified as high.

Proof of Concept

Evident from the code.

Tools Used

Manual Review

Replace 0 with i in the loop.

#0 - c4-pre-sort

2023-04-20T14:16:22Z

0xA5DF marked the issue as duplicate of #941

#1 - c4-judge

2023-05-18T14:23:17Z

hansfriese marked the issue as satisfactory

#2 - c4-judge

2023-05-18T14:32:26Z

hansfriese changed the severity to 2 (Med Risk)

Findings Information

🌟 Selected for report: yellowBirdy

Also found by: BenRai, ChrisTina, GreedyGoblin, Norah, carrotsmuggler

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
duplicate-874

Awards

153.271 USDC - $153.27

External Links

Lines of code

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

Vulnerability details

Impact

The function deny() can be used on any position to cancel it immediately within 3 days of creation (at least). This is implemented to ensure bad positions do not go through. This function imposes a cooldown on the position until expiry, as shown below.

cooldown = expiration; 

This trips off the noCooldown modifier which checks for the same.

modifier noCooldown() {
        if (block.timestamp <= cooldown) revert Hot();
        _;
    }

The function withdrawCollateral is also modified with this modifier, and thus essentially locks down their collateral until the expiration date is passed.

This can be used to cause locked funds or opportunity losses to position openers by any person holding enough equity tokens. Since the position owner is already losing their opening fee, it seems unnecessary to continue to lock the collateral as well until expiry.

Proof of Concept

Calling deny on any position locks its collateral.

Tools Used

Manual Review

Allow collateral to be withdrawn when denied.

#0 - c4-pre-sort

2023-04-24T07:11:41Z

0xA5DF marked the issue as duplicate of #874

#1 - c4-judge

2023-05-17T18:53:08Z

hansfriese changed the severity to 2 (Med Risk)

#2 - c4-judge

2023-05-18T13:21:58Z

hansfriese marked the issue as satisfactory

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