Company Simulator

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

[H-02] - Owner can manipulate share price to steal investor funds

Root + Impact

Description

The share price calculation relies on the company's net_worth (company_balance - holding_debt) divided by the issued_shares. The owner has control over both the company_balance (via fund_cyfrin(0)) and the issued_shares (via increase_share_cap()).

A malicious owner can exploit this to perform a "rug pull" on investors by inflating the share price, attracting investment, and then deflating the share price to zero before the investor can withdraw.

Attack Steps:

  1. Inflate: Owner funds the company with a massive amount of ETH (e.g., 10,000 ETH) using fund_cyfrin(0) when issued_shares is low. This instantly and artificially inflates the share price.

  2. Attract: Investor buys shares at the inflated price.

  3. Deflate: Owner calls pay_holding_debt() with a large msg.value. If msg.value exceeds the debt, the excess is added to company_balance. The owner can then call produce() repeatedly, draining the company_balance and setting the share price to near zero.

  4. Result: The investor's shares are now worthless, and the owner has control of the withdrawn funds.

# Root cause in the codebase (Cyfrin_Hub.vy)
# The owner can increase company_balance without issuing shares
@external
def fund_cyfrin(action: uint256):
# ...
if action == 0:
assert msg.sender == OWNER, "Not the owner!!!"
self.company_balance += msg.value # @> Owner adds value without issuing shares
# ...
# The share price calculation is vulnerable to this inflation/deflation
share_price: uint256 = (
net_worth // max(self.issued_shares, 1) # @> Price is directly tied to balance
if self.issued_shares > 0
else INITIAL_SHARE_PRICE
)

Risk

Likelihood: High
The owner has direct, immediate control over the two variables (company_balance and issued_shares) that determine the share price. The attack is a single-transaction exploit for the inflation phase and a simple sequence of calls for the deflation phase.

Impact: High
The vulnerability allows the owner to steal all investor funds by manipulating the share price to zero, resulting in a complete loss of capital for all non-owner investors. This is a direct financial loss and a protocol failure.

Proof of Concept

  1. Owner inflates share price by funding company with 1000 ETH via fund_cyfrin(0).

  2. Investor buys shares at inflated price (100 ETH investment).

  3. Owner deflates share price by calling produce(50000), draining 500 ETH from balance.

  4. Investor's share value drops by >30%, demonstrating the manipulation.

Supporting Code:

# Test to demonstrate H-02: Share Price Manipulation Attack
import boa
from eth_utils import to_wei
def test_share_price_manipulation():
"""
Demonstrates how the owner can manipulate share price to disadvantage investors.
"""
# Deploy contracts
industry = boa.load("src/Cyfrin_Hub.vy")
engine = boa.load("src/CustomerEngine.vy", industry.address)
# Setup
owner = industry.OWNER_ADDRESS()
investor = boa.env.generate_address("investor")
boa.env.set_balance(owner, to_wei(10000, "ether"))
boa.env.set_balance(investor, to_wei(200, "ether"))
# Owner sets engine
with boa.env.prank(owner):
industry.set_customer_engine(engine.address)
# Step 1: Owner inflates share price by funding company
with boa.env.prank(owner):
industry.fund_cyfrin(0, value=to_wei(1000, "ether"))
initial_share_price = industry.get_share_price()
print(f"Initial share price: {initial_share_price / 10**18} ETH")
# Step 2: Investor buys shares at inflated price
investor_investment = to_wei(100, "ether")
with boa.env.prank(investor):
industry.fund_cyfrin(1, value=investor_investment)
investor_shares = industry.shares(investor)
share_price_after_investment = industry.get_share_price()
print(f"Investor received {investor_shares} shares")
print(f"Share price after investment: {share_price_after_investment / 10**18} ETH")
# Step 3: Owner deflates share price by producing items (draining balance)
# Production cost is 0.01 ETH per item
# Owner produces 50,000 items, costing 500 ETH
with boa.env.prank(owner):
industry.produce(50000)
share_price_after_production = industry.get_share_price()
print(f"Share price after owner production: {share_price_after_production / 10**18} ETH")
# Calculate investor's loss
investor_value_before = investor_shares * share_price_after_investment
investor_value_after = investor_shares * share_price_after_production
loss_percentage = ((investor_value_before - investor_value_after) / investor_value_before) * 100
print(f"\nInvestor's share value before manipulation: {investor_value_before / 10**18} ETH")
print(f"Investor's share value after manipulation: {investor_value_after / 10**18} ETH")
print(f"Loss percentage: {loss_percentage:.2f}%")
# Assertions
assert investor_value_after < investor_value_before, "Investor value should decrease"
assert loss_percentage > 30, f"Expected >30% loss, got {loss_percentage:.2f}%"
print(f"\n✓ PoC Confirmed: Owner manipulated share price, causing {loss_percentage:.2f}% loss to investor")

Recommended Mitigation

The owner should not be able to increase the company_balance without a corresponding increase in issued_shares.

@external
@payable
def fund_cyfrin(action: uint256):
# ...
if action == 0:
assert msg.sender == OWNER, "Not the owner!!!"
- self.company_balance += msg.value
+ # Mitigation: Owner funding should be treated as a loan or removed entirely.
+ # If kept, it should be treated as a loan that must be repaid.
+ # For simplicity, we suggest removing the owner's ability to fund without shares.
+ assert False, "Owner funding is disabled to prevent share price manipulation."
# ...

Alternatively, the share price calculation should exclude owner-funded ETH from the net_worth calculation.

Updates

Lead Judging Commences

0xshaedyw Lead Judge
9 days ago
0xshaedyw Lead Judge 8 days ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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