Company Simulator

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

No validation that company is a contract (trust / misconfiguration risk)

Root + Impact

Description

  • Expected behavior: On deployment, company should be a contract address implementing CompanyGame interface; otherwise calls (e.g., reputation() and sell_to_customer) may misbehave.

  • Actual behavior: __init__ sets self.company = _company with no validation.

No check that _company has code (is a contract). If a non-contract EOA is provided (accidentally or maliciously), raw_call will still succeed (transferring ETH), but reputation() staticcall may fail or return 0. A malicious address could be set, and the company logic (in CompanyGame) might be absent or malicious.

// Root cause in the codebase with @> marks to highlight the relevant section
@deploy
def __init__(_company: address):
@> self.company = _company

Risk

Likelihood

Medium :deploy-time mistake or malicious config possible.

Impact

1.Misconfiguration leading to lost funds (paying an EOA).

2.If company is a malicious contract, it can behave arbitrarily when sell_to_customer is called.

Proof of Concept

It implements the expected reputation() view and forwards any received ETH to the attacker sink address.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @notice Minimal malicious CompanyGame implementation for PoC
contract MaliciousCompany {
address payable public sink;
constructor(address payable _sink) {
require(_sink != address(0), "invalid sink");
sink = _sink;
}
// Always return high reputation so CustomerEngine proceeds
function reputation() external pure returns (uint256) {
return 100;
}
// Accept sale calls and forward ETH immediately to sink
receive() external payable {
// forward all received ETH to attacker sink
(bool ok, ) = sink.call{value: msg.value}("");
// ignore result (sink should be EOA in PoC)
}
// In case CustomerEngine calls sell_to_customer via method id (raw_call), fallback will trigger
fallback() external payable {
(bool ok, ) = sink.call{value: msg.value}("");
// ignore result
}
}

Recommended Mitigation

Validate _company is a contract at deployment:

Add a setter for company address restricted to owner (if governance exists), with same check, and emit event.


- remove this code
+ add this code
@deploy
def __init__(_company: address):
- self.company = _company
+ # ensure the company address is a contract
+ size: uint256 = 0
+ size = extcodesize(_company)
+ assert size > 0, "company must be a contract"
+ self.company = _company
Updates

Lead Judging Commences

0xshaedyw Lead Judge
4 days ago
0xshaedyw Lead Judge 3 days ago
Submission Judgement Published
Invalidated
Reason: Out of scope

Support

FAQs

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