Company Simulator

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

Accounting Desynchronization Risk

Root + Impact

Description

  • The normal behavior in sell_to_customer updates company_balance with a calculated revenue (requested * SALE_PRICE) after a successful sale, assuming the caller (engine) sends the exact amount.

  • The specific issue is that it uses the calculated value instead of msg.value, allowing desynchronization if the trusted engine sends a mismatched amount (e.g., due to compromise or error), leading to internal balance not matching actual ETH held.

# In CompanyGame.sell_to_customer:
if self.inventory >= requested:
self.inventory -= requested
@> revenue: uint256 = requested * SALE_PRICE # Calculated, not msg.value
@> self.company_balance += revenue # Updates with calculation, ignoring actual msg.value

Risk

Likelihood: Low

  • Compromise of the trusted CustomerEngine address after being set once by the owner.

  • Rare misconfiguration or external call with incorrect value, but restricted to engine.

Impact: Medium

  • Desynchronized accounting could falsely indicate insolvency, preventing functions like production or payouts.

  • Indirect fund risk through decisions based on incorrect balance, such as over-payouts or unnecessary debt accumulation.

Proof of Concept

The test funds and produces inventory, pranks as the engine to call sell_to_customer with mismatched ETH value (e.g., 0.03 instead of 0.02), then asserts that internal company_balance updates with calculated revenue while actual contract ETH differs, showing desync.

import boa
from eth_utils import to_wei
def test_accounting_desynchronization_risk(industry_contract, customer_engine_contract, OWNER):
# Arrange: Fund and produce inventory
boa.env.set_balance(OWNER, to_wei(10, "ether"))
with boa.env.prank(OWNER):
industry_contract.fund_cyfrin(0, value=to_wei(10, "ether"))
industry_contract.produce(10)
tracked_before = industry_contract.company_balance()
actual_eth_before = boa.env.get_balance(industry_contract.address)
# Act: Simulate compromised engine sending mismatched value (prank as engine)
requested = 1
mismatched_value = to_wei(0.03, "ether") # > 0.02 ETH for 1 item
boa.env.set_balance(customer_engine_contract.address, mismatched_value) # Fund engine to send value
with boa.env.prank(customer_engine_contract.address):
industry_contract.sell_to_customer(requested, value=mismatched_value)
# Assert: Tracked balance updated with calculated revenue (0.02), but actual ETH +0.03
calculated_revenue = requested * (2 * 10**16)
expected_tracked = tracked_before + calculated_revenue # Ignoring holding cost for simplicity
actual_tracked = industry_contract.company_balance()
actual_eth_after = boa.env.get_balance(industry_contract.address)
assert actual_tracked == expected_tracked, "Tracked uses calculated"
assert actual_eth_after == actual_eth_before + mismatched_value, "Actual ETH uses msg.value"
assert actual_tracked != actual_eth_after, "Desynchronization occurred"

Recommended Mitigation

Add an assert to verify msg.value == requested * SALE_PRICE, and update company_balance += msg.valueinstead of calculated revenue, ensuring tracking matches actual inflows.

def sell_to_customer(requested: uint256):
...
if self.inventory >= requested:
+ assert msg.value == requested * SALE_PRICE, "Invalid payment amount!!!"
self.inventory -= requested
- revenue: uint256 = requested * SALE_PRICE
- self.company_balance += revenue
+ self.company_balance += msg.value # Use actual received
...
}
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.