Company Simulator

First Flight #51
Beginner FriendlyDeFi
100 EXP
View results
Submission Details
Severity: medium
Valid

Investors Can Overpay Without Receiving Refund When Share Cap Is Reached

Description

The fund_investor function allows users to invest ETH in exchange for company shares. The function calculates how many shares the investment can purchase, then caps the shares at the available amount if it exceeds the public share cap.

However, the function adds the ENTIRE msg.value to company_balance regardless of how many shares were actually issued, meaning investors who send more ETH than needed will overpay without receiving any refund.

@payable
@internal
def fund_investor():
assert msg.value > 0, "Must send ETH!!!"
assert (
self.issued_shares <= self.public_shares_cap
), "Share cap reached!!!"
assert (self.company_balance > self.holding_debt), "Company is insolvent!!!"
# Calculate shares based on contribution
net_worth: uint256 = 0
if self.company_balance > self.holding_debt:
net_worth = self.company_balance - self.holding_debt
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
# Cap shares if exceeding visible limit
available: uint256 = self.public_shares_cap - self.issued_shares
@> if new_shares > available: # Line 324
@> new_shares = available # Line 325 - Shares are capped
self.shares[msg.sender] += new_shares
self.issued_shares += new_shares
@> self.company_balance += msg.value # Line 329 - Full payment is kept!

Risk

Likelihood:

  • This occurs whenever an investor sends more ETH than the remaining share capacity allows

  • As the share cap approaches the limit, this becomes increasingly likely

  • Malicious investors could exploit this by front-running and intentionally overpaying

Impact:

  • Investors lose excess ETH without receiving equivalent value in shares

  • The company receives free capital from overpayment

  • This creates an unfair wealth transfer from investors to existing shareholders

  • Later investors are especially vulnerable when approaching the share cap

Proof of Concept

# Scenario: Only 1000 shares remaining before cap is reached
# Share price = 0.001 ETH per share
# Investor sends 10 ETH thinking they'll get 10,000 shares
available_shares = 1000 # Only 1000 shares left
share_price = 0.001 ETH
investor_payment = 10 ETH
# What investor expects:
expected_shares = 10 ETH // 0.001 ETH = 10,000 shares
# What actually happens:
new_shares = min(expected_shares, available_shares) = 1000 shares
actual_value = 1000 * 0.001 ETH = 1 ETH
# But company keeps full 10 ETH!
company_balance += 10 ETH
# Result: Investor pays 10 ETH but only gets 1 ETH worth of shares
# Loss: 9 ETH

Recommended Mitigation

@payable
@internal
def fund_investor():
assert msg.value > 0, "Must send ETH!!!"
assert (
self.issued_shares <= self.public_shares_cap
), "Share cap reached!!!"
assert (self.company_balance > self.holding_debt), "Company is insolvent!!!"
# Calculate shares based on contribution
net_worth: uint256 = 0
if self.company_balance > self.holding_debt:
net_worth = self.company_balance - self.holding_debt
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
# Cap shares if exceeding visible limit
available: uint256 = self.public_shares_cap - self.issued_shares
if new_shares > available:
new_shares = available
+ # Calculate actual cost and refund excess
+ actual_cost: uint256 = new_shares * share_price
+ excess: uint256 = msg.value - actual_cost
+ if excess > 0:
+ send(msg.sender, excess)
self.shares[msg.sender] += new_shares
self.issued_shares += new_shares
- self.company_balance += msg.value
+ self.company_balance += actual_cost
if self.share_received_time[msg.sender] == 0:
self.share_received_time[msg.sender] = block.timestamp
log SharesIssued(investor=msg.sender, amount=new_shares)
Updates

Lead Judging Commences

0xshaedyw Lead Judge
10 days ago
0xshaedyw Lead Judge 8 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Medium – Excess Contribution Not Refunded

Investor ETH above share cap is accepted without refund or shares, breaking fairness.

Support

FAQs

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