GoldReserve · Missing evidence
Syntax error
The reference repair with a syntax error injected.
The patched contract does not compile, so no test could run. A build failure is never read as a blocked exploit.
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@@ -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 inputsNeeds 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.
$ 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 --sha256Expected 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 repositoryThe 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.