Company Simulator

First Flight #51
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: high
Likelihood: medium

trigger_demand can cause Refund Loss

Author Revealed upon completion

Root + Impact

Description

trigger_demand refunds any overpayment with Vyper’s send, which forwards only 2300 gas and returns a boolean. The code ignores that boolean, so a caller with a complex fallback (or one that always reverts) silently loses the refund.

excess: uint256 = msg.value - total_cost
if excess > 0:
@> send(msg.sender, excess)

Risk

Likelihood: Many smart-contract callers require more than 2300 gas in their fallback (e.g., logging, accounting hooks). Any such contract interacting with the engine and overpaying hits the failure path. Attackers can also design grief contracts to exploit the blind refund.

Impact:

  • Honest users permanently lose their change, contradicting the contract’s promise to refund.

  • MEV bots or aggregators may stop integrating, reducing volume.

  • Attackers can create automated griefers that force routers to bleed ETH each time they interact.

Proof of Concept

  1. Deploy helper GreedyRefund whose fallback emits an event (consuming >2300 gas).

  2. Call trigger_demand from that contract with deliberate overpayment.

  3. Observe no refund was received; the transaction succeeded anyway.

Example test scaffold:

def test_refund_lost(engine, hub, greedy):
hub.set_customer_engine(engine.address, sender=hub.owner())
engine.trigger_demand(value=10 * ITEM_PRICE, sender=greedy)
assert greedy.balance == 0 # excess never returned

Recommended Mitigation

  1. Replace send with raw_call(msg.sender, b"", value=excess, revert_on_failure=True) so the transaction reverts if the refund fails.

  2. Alternatively, assert the boolean returned by send and revert on failure if you must cap gas.

  3. Add regression tests covering both EOAs and contracts as demand callers.

Patch sketch:

-if excess > 0:
- send(msg.sender, excess)
+if excess > 0:
+ raw_call(msg.sender, b"", value=excess, revert_on_failure=True)

Support

FAQs

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