Expected behavior: Functions that perform external calls (including raw_call or send/transfer) must protect critical state updates with a non-reentrancy guard (e.g., @nonreentrant in Vyper or an explicit lock) so external contract callbacks cannot re-enter and corrupt state.
Actual behavior: CustomerEngine.trigger_demand() and some hub functions perform external calls (e.g., raw_call(self.company, ...), raw_call(msg.sender, ..., value=payout)) without applying an explicit non-reentrancy decorator or manual reentrancy lock. A malicious counterparty can re-enter via callbacks and cause unexpected state transitions.
Likelihood
Medium — depends on whether the external counterparty is attacker-controlled; trivial to exploit in a test environment or if the owner sets attacker contract as company. Given the code allows owner to set CUSTOMER_ENGINE and external calls to arbitrary contract, likelihood is material.
Impact
1.State inconsistency, double-withdrawal, or accounting manipulation via callbacks.
2.Unexpected re-execution of logic that relies on invariant assumptions (e.g., daily counters, shares).
3.Potential for draining ETH or duplicating rewards if a sequence of external calls and state updates is poorly ordered.
This PoC demonstrates that callbacks from an external counterparty can invoke public/external functions again during a transaction, so a nonreentrant guard is required.
Add nonreentrancy decorator or manual lock. Vyper supports @nonreentrant (version-dependent) or manual lock field.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.