The EggVault
contract allows the owner (assumed to be a trusted admin or game operator) to call setEggNFT
and arbitrarily update the reference to the NFT contract, even after players have deposited their eggs.
This creates a critical integrity risk: the vault's storedEggs
and eggDepositors
mappings are keyed off token IDs, but these IDs are meaningless without a fixed NFT contract address. If the vault later points to a different NFT contract:
The vault's stored tokenId
s no longer represent the same assets.
Previously deposited NFTs remain locked in the original contract but eggNFT.transferFrom()
will act on a different token registry.
Withdrawals may fail or send unrelated NFTs to the wrong users.
This behavior undermines the assumptions players make when interacting with the vault, especially if they expect their deposited eggs to remain secure and retrievable.
Medium
This is not necessarily exploitable for theft if the vault owner is trusted, but it enables bricking or invalidating user deposits.
In a live game, it can lead to loss of access, broken game mechanics, or potential user distrust.
Even well-intentioned updates (e.g., contract upgrades, bug fixes) can result in asset loss if not handled carefully.
In the context of EggHunt, this could disrupt in-game rewards, scoring systems, or vault-based mechanics tied to event progression.
The impact is mitigated somewhat if:
The owner is a multisig or DAO with strong governance,
All users understand the vault may change behavior mid-game.
However, it still breaks the immutability and trust guarantees expected in smart contract systems.
Scenario:
A player deposits Egg #1 (from NFT contract A) into the vault.
The vault stores eggDepositors[1] = player
, and marks the egg as deposited.
Mid-game, the owner calls setEggNFT(address(B))
, switching to a new EggstravaganzaNFT contract.
When the player tries to withdraw Egg #1, the vault calls transferFrom(address(this), player, 1)
on contract B — which has no knowledge of the original egg.
If tokenId 1 exists in contract B, the wrong token may be transferred.
If not, the withdrawal may fail permanently.
The actual egg remains locked in contract A forever.
Option 1: Make the NFT address immutable after initialization
This ensures the NFT contract can only be set once, aligning with the assumption that the game operates on a single NFT registry per deployment.
Option 2: Add explicit constraints
Prevent changes to the NFT address if any eggs are currently deposited.
Option 3: Use constructor-only assignment If the contract architecture allows, simply set the eggNFT
address in the constructor and remove setEggNFT
entirely.
Trusting the owner to not abuse a function does not mean the function should be allowed if it creates unrecoverable failure modes.
Even accidental misuse or poor upgrade practices can trigger this issue.
In competitive or adversarial games, the ability to brick eggs (intentionally or by mistake) is a valid concern.
Changing the NFT contract address doesn't update the storedEggs and eggDepositors mappings
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.