Company Simulator

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

Owner not assigned correctly in constructor (critical)

Root + Impact

Description

  • Expected: the OWNER state variable is set so owner-only checks (msg.sender == OWNER) work.

  • Actual: constructor uses OWNER = msg.sender instead of self.OWNER = msg.sender, so OWNER may not be initialized as intended.

...

Using bare OWNER = msg.sender does not assign the contract storage variable in Vyper; it creates/uses a local name instead. As a result owner checks throughout the contract (assert msg.sender == OWNER) will behave incorrectly ; either always failing or comparing against an uninitialized value -breaking access control and enabling unauthorized callers or preventing owner actions.

// Root cause in the codebase with @> marks to highlight the relevant section
@deploy
def __init__():
...
@> OWNER = msg.sender
...

Risk

Likelihood: High (the bug is deterministic and triggered on every deployment)

Impact

If OWNER is never initialized:

1.Any call checking assert msg.sender == OWNER will always fail (breaking core owner-only operations like produce(), increase_share_cap(), set_customer_engine() etc.).

2.In some compiler edge cases, the variable may default to 0x000...000, meaning nobody can satisfy the owner check — effectively locking owner functionality permanently (a denial-of-service).

3.Alternately, if mis-scoped incorrectly, OWNER could behave as an uninitialized global and allow any caller to appear valid — compromising access control and enabling unauthorized takeover.

Proof of Concept

Deployer (acct0) can’t call produce() because the OWNER variable was never set in storage — confirming broken access control.

# SPDX-License-Identifier: MIT
# Example: Foundry-style minimal PoC
from vyper import compile_code
from web3 import Web3
from eth_tester import EthereumTester, PyEVMBackend
w3 = Web3(Web3.EthereumTesterProvider(EthereumTester(PyEVMBackend())))
acct0 = w3.eth.accounts[0] # deployer (intended owner)
acct1 = w3.eth.accounts[1] # attacker / outsider
source = open("CompanyGame.vy").read() # or Cyfrin_Hub.vy (buggy version)
compiled = compile_code(source, ["abi", "bytecode"])
Company = w3.eth.contract(abi=compiled["abi"], bytecode=compiled["bytecode"])
# 1. Deploy contract
tx_hash = Company.constructor().transact({"from": acct0})
addr = w3.eth.get_transaction_receipt(tx_hash)["contractAddress"]
company = w3.eth.contract(address=addr, abi=compiled["abi"])
# 2. Try to call an owner-only function as the deployer
try:
tx = company.functions.produce(1).transact({"from": acct0})
w3.eth.wait_for_transaction_receipt(tx)
print("❌ Bug NOT reproduced (unexpected)")
except Exception as e:
print("✅ Owner check failed due to bad assignment:", e)
# 3. Optional: see if OWNER variable is zero
print("Stored OWNER:", company.functions.OWNER_ADDRESS().call())

Recommended Mitigation

This ensures that the immutable storage variable OWNER is initialized correctly at deployment, preserving all ownership checks and preventing denial-of-service or unauthorized control.

- remove this code
+ add this code
@deploy
def __init__():
- OWNER = msg.sender
+ self.OWNER = msg.sender
Updates

Lead Judging Commences

0xshaedyw Lead Judge
6 days ago
0xshaedyw Lead Judge 4 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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