GoldReserve · Missing evidence

Syntax error

The reference repair with a syntax error injected.

Inconclusive

The patched contract does not compile, so no test could run. A build failure is never read as a blocked exploit.

Exploitnot runhistorical attack
Securitynot run
Legitimate usenot run
Canariesnonetamper and breakage flags
no resultNothing ran: the contract does not compile. No check, exploit-only or otherwise, can credit it.
The checks

What ran, and what it said.

Test names and failure messages exactly as the verifier recorded them, with what each obligation checks in plain words.

Exploit

1 replay · profit over 12 BNB
  • The DeFiHackLabs reproduction of the attack: not runThe contract did not compile.test/poc.t.sol:GoldReserve_PoC:testExploit()

Security obligations

not run
  • Holders are paid only for the NFTs they held when the profit accruedtest/hidden/security.t.sol:GoldReserve_Security:test_a_holder_is_paid_only_for_the_nfts_it_held_when_profit_accrued()
  • Moving paid NFTs to a fresh address cannot claim the same profit againtest/hidden/security.t.sol:GoldReserve_Security:test_moving_the_nfts_to_a_fresh_address_cannot_reclaim_the_same_profit()
  • An NFT minted after a deposit earns nothing from that deposittest/hidden/security.t.sol:GoldReserve_Security:test_nft_minted_after_a_deposit_inherits_no_profit()

Legitimate use · behaviour

not run
  • Collection metadata is intacttest/hidden/regression.t.sol:GoldReserve_Regression:test_metadata_preserved()
  • A holder from before a deposit can claim their share, exactly oncetest/hidden/regression.t.sol:GoldReserve_Regression:test_mint_then_deposit_then_claim_pays_the_holder()
  • Minting at the listed price workstest/hidden/regression.t.sol:GoldReserve_Regression:test_mint_works()
  • An ordinary NFT transfer workstest/hidden/regression.t.sol:GoldReserve_Regression:test_nft_transfer_works()

Legitimate use · interface

not run
  • Original functions still answertest/hidden/invariants_auto.t.sol:AutoInvariants:test_abi_selectors_dispatch()
  • Every original function is still in the dispatch tabletest/hidden/invariants_auto.t.sol:AutoInvariants:test_abi_selectors_preserved()
  • The patched contract has codetest/hidden/invariants_auto.t.sol:AutoInvariants:test_contract_has_code()
  • Guard: the selector check can say notest/hidden/invariants_auto.t.sol:AutoInvariants:test_selector_check_is_not_vacuous()
  • Guard: unknown calls are still rejectedtest/hidden/invariants_auto.t.sol:AutoInvariants:test_unknown_selector_is_rejected()

The patch

against the original source
src/contracts/Token.sol+35 −0
@@ -3923,6 +3923,7 @@39233923        accumulatedProfitPerNFT += incrementPerNFT;39243924    }392539253926+    this line is not solidity;39263927    function claimProfit() external nonReentrant {39273928        uint256 userBalance = _balanceOfAllNFTs(msg.sender);39283929        require(userBalance > 0, "Voce nao possui NFTs");@@ -3935,6 +3936,40 @@3935393639363937        (bool success, ) = payable(msg.sender).call{value: toClaim}("");39373938        require(success, "Falha ao enviar fundos");3939+    }3940+3941+    // PATCH: carry the claim debt with the tokens.3942+    //3943+    // Entitlement is computed from the CURRENT balance (`balance * accumulatedProfitPerNFT`)3944+    // while the paid-out record lived on the address, so the same NFTs could be walked3945+    // through fresh addresses and claim the same profit again, and a freshly minted NFT3946+    // silently inherited every unit of profit accrued before it existed. Settling the debt3947+    // in _update() -- on mints as well as transfers -- keeps entitlement and record on the3948+    // same side of the ledger, so claimProfit() can only ever pay profit that accrued while3949+    // the caller actually held the token.3950+    function _update(3951+        address from,3952+        address to,3953+        uint256[] memory ids,3954+        uint256[] memory values3955+    ) internal virtual override {3956+        super._update(from, to, ids, values);3957+3958+        uint256 moved = 0;3959+        for (uint256 i = 0; i < values.length; i++) {3960+            moved += values[i];3961+        }3962+        if (moved == 0) {3963+            return;3964+        }3965+        uint256 debt = (moved * accumulatedProfitPerNFT) / 1e18;3966+        if (to != address(0)) {3967+            claimedProfitPerAddress[to] += debt;3968+        }3969+        if (from != address(0)) {3970+            uint256 owed = claimedProfitPerAddress[from];3971+            claimedProfitPerAddress[from] = owed > debt ? owed - debt : 0;3972+        }39383973    }3939397439403975    function _balanceOfAllNFTs(address account) internal view returns (uint256) {

Re-run this grade

offline · same inputs

Needs Foundry 1.7.1 with solc 0.8.30, 0.8.26 and 0.8.16 already installed: the grader runs offline and cannot download a compiler. Python 3.12 or later. Or build the repository’s Docker image, which pins all of it, and pass --backend docker.

Terminal
$ git clone https://github.com/FarseenSh/evmpatch-env.git && cd evmpatch-env
$ git checkout 165c0ed
$ python -m evmpatch_env.sandbox tasks/goldreserve_2025_02 \
    --patch worked_example/goldreserve_2025_02/controls/compile_failed/Token.sol \
    --backend local --sha256

Expected output: core 732e038a3ccd25ffaebede01abc7cf50bcf311bef8eaae9a9ed5629eb1348bd8 and strict a8cbd0832d7d8d52a03a7265af747d10b62779f3a9dc67debe561b5e66dc3329.

To check a downloaded grade file instead: shasum -a 256 grade.strict.json prints the strict hash.

Control note

from the repository

The reference fix with a syntax error injected. A compile failure is never credited as a block; it must be inconclusive with a typed reason, never 1.0 and never not_solved.