Company Simulator

First Flight #51
Beginner FriendlyDeFi
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Division by Zero in fund_investor() Share Price Calculation

Root + Impact

Description

  • Normal Behavior:
    The fund_investor() function allows users to invest ETH and receive shares proportional to their contribution.
    The price per share is calculated using the company’s net worth (company_balance - holding_debt) divided by the number of issued shares.


  • Specific Issue:
    When issued_shares > 0 but net_worth is too low (zero or less than issued_shares),
    the division result net_worth // issued_shares becomes 0.
    Later, the code uses this zero value to calculate how many shares to issue, causing a division by zero revert.

// Root cause in the codebase with @> marks to highlight the relevant section
share_price: uint256 = (
net_worth // max(self.issued_shares, 1)
if self.issued_shares > 0
else INITIAL_SHARE_PRICE
)
@> new_shares: uint256 = msg.value // share_price # <-- share_price may be 0 here

Risk

Likelihood:

1.This will occur whenever the company’s balance roughly equals or falls below its holding debt, making net_worth = 0.

2.It’s also likely to happen during insolvency or low-liquidity periods when no capital has been injected by the owner.

Impact:

1.All new investments will revert, effectively freezing the funding process.

2.The company cannot recover from insolvency through new investor funding, causing a permanent Denial-of-Service.

Proof of Concept

Explanation:

When issuedShares > 0 but the company’s netWorth equals zero, the computed sharePrice becomes 0, causing msg.value / sharePrice in fundInvestor() to revert with a division-by-zero panic (0x12) — effectively freezing the investment process and demonstrating the vulnerability.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/*
PoC: reproduces division-by-zero in fundInvestor() when computed sharePrice == 0
This mirrors the vulnerable logic from the Vyper contract:
sharePrice = netWorth / issuedShares // can be 0
newShares = msg.value / sharePrice // division by zero -> revert
*/
contract CyfrinPoC {
uint256 public companyBalance;
uint256 public holdingDebt;
uint256 public issuedShares;
uint256 public constant INITIAL_SHARE_PRICE = 1e15; // 0.001 ETH
// helpers to set state (only for PoC/testing)
function setCompanyBalance(uint256 _v) external { companyBalance = _v; }
function setHoldingDebt(uint256 _v) external { holdingDebt = _v; }
function setIssuedShares(uint256 _v) external { issuedShares = _v; }
// vulnerable function that mimics fund_investor() logic
function fundInvestor() external payable {
require(msg.value > 0, "Must send ETH");
uint256 netWorth = 0;
if (companyBalance > holdingDebt) {
netWorth = companyBalance - holdingDebt;
}
uint256 sharePrice;
if (issuedShares > 0) {
// vulnerable: integer division can yield 0 when netWorth < issuedShares
sharePrice = netWorth / issuedShares;
} else {
sharePrice = INITIAL_SHARE_PRICE;
}
// <-- division by zero occurs here when sharePrice == 0
uint256 newShares = msg.value / sharePrice;
// minimal state updates to mirror original behavior
issuedShares += newShares;
companyBalance += msg.value;
}
}

Recommended Mitigation

Explanation

Ensure that share_price never equals zero before performing the division.

Either fallback to a minimum price or reject investments when the company’s net worth is zero.

- remove this code
+ add this code
@@ function fund_investor()
- share_price: uint256 = (
- net_worth // max(self.issued_shares, 1)
- if self.issued_shares > 0
- else INITIAL_SHARE_PRICE
- )
- new_shares: uint256 = msg.value // share_price
+ if self.issued_shares > 0:
+ computed_price: uint256 = net_worth // self.issued_shares
+ # Prevent zero-priced shares
+ if computed_price == 0:
+ share_price: uint256 = INITIAL_SHARE_PRICE
+ else:
+ share_price: uint256 = computed_price
+ else:
+ share_price: uint256 = INITIAL_SHARE_PRICE
+
+ new_shares: uint256 = msg.value // share_price
Updates

Lead Judging Commences

0xshaedyw Lead Judge
6 days ago
0xshaedyw Lead Judge 4 days ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.