Reproduced Exploit
Gigaverse — mintUsername charges 0 for a name listed at 0.005 ETH
1. The manager sets the username price with AccountSystem.setMintPrice. mintWithEth reads that table and reverts unless msg.value covers it. 2. GigaNameNFT.mintUsername checks ETH_MINT_PRICE_CID on its own table. Nothing writes that cell, so it is 0. 3. The attacker calls mintUsername(attacker, "al…
Chain
Other
Category
logic
Date
Jan 2025
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 53288-h-05-zero-username-price-when-it-is-minted-with-mintusername. Standalone Foundry PoC and full write-up: 53288-h-05-zero-username-price-when-it-is-minted-with-mintusername_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/logic/fee-calculation · vuln/logic/price-calculation · vuln/logic/wrong-condition
Reproduction: a self-contained Foundry PoC that compiles and runs with only
forge-std— no fork, no RPC. Full trace: output.txt. PoC: test/53288-h-05-zero-username-price-when-it-is-minted-with-mintusername_exp.sol.
AuditVault taxonomy: lang/solidity · has/github · severity/high · sector/perpetuals · genome: fee-accounting · logic/fee-calculation · loss-of-funds/fee-theft
Key info#
| Impact | HIGH — a username the protocol lists at 0.005 ETH (5000000000000000 wei) is minted for 0 wei, and the caller receives the name |
| Protocol | Gigaverse — AccountSystem.mintWithEth / GigaNameNFT.mintUsername |
| Vulnerable code | GigaNameNFT.mintUsername — require(msg.value >= getTableUint256Value(ETH_MINT_PRICE_CID)) |
| Bug class | Fee is read from the name-NFT table, which stays 0, instead of the AccountSystem table the manager writes |
| Finding | Pashov Audit Group — Gigaverse, 2025-01-18 · #53288 (H-05) |
| Report | Gigaverse-security-review_2025-01-18 |
| Source | AuditVault |
| Status | Audit finding, reproduced as a local synthetic |
| Compiler | solc 0.8.27, cancun, via_ir |
This is an audit finding, not an on-chain incident. mintUsername is copied verbatim from the report. The two tables are the minimum scaffolding that makes the wrong read observable: AccountSystem's column holds 0.005 ether, GigaNameNFT's column holds 0.
TL;DR#
- The manager sets the username price with
AccountSystem.setMintPrice.mintWithEthreads that table and reverts unlessmsg.valuecovers it. GigaNameNFT.mintUsernamechecksETH_MINT_PRICE_CIDon its own table. Nothing writes that cell, so it is0.- The attacker calls
mintUsername(attacker, "alice")with no ETH. The check is0 >= 0.confirmMintassigns the name. - Harm: listed price
5000000000000000wei, name-table price0, ETH charged0, protocol fees0, token id1, owner is the attacker, name isalice. - Control:
mintWithEth{value: 0.005 ether}mintsboband records a charge of5000000000000000.mintWithEth{value: 0}reverts and does not mint.
The vulnerable code#
From the report, preserved verbatim:
function mintUsername(address to, string memory username) external payable {
// @> VULN: price is read from the GigaNameNFT table, which stays 0
require(msg.value >= getTableUint256Value(ETH_MINT_PRICE_CID), "Insufficient payment");
confirmMint(to, username);
}
getTableUint256Value reads NAME_TABLE, not ACCOUNT_TABLE:
function getTableUint256Value(uint256 columnId) public view returns (uint256) {
return dataStore.getUint256(NAME_TABLE, columnId);
}
The path that charges the real price:
function mintWithEth(address to, string memory username) external payable returns (uint256 id) {
uint256 price = mintPrice(); // AccountSystem table
require(msg.value >= price, "Insufficient payment");
feesCollected += msg.value;
id = names.mintUsernameByMinter{value: msg.value}(to, username);
}
Recommended fix from the report: both contracts must read the AccountSystem table.
- require(msg.value >= getTableUint256Value(ETH_MINT_PRICE_CID), "Insufficient payment");
+ require(msg.value >= accountSystem.mintPrice(), "Insufficient payment");
Root cause#
ETH_MINT_PRICE_CID is a column id, not a global variable. AccountSystem.setMintPrice writes it under the account table. mintUsername loads it under the name-NFT table. The two cells are independent, the name-NFT cell has no setter, and the default is 0. Any caller can satisfy the payment check by sending nothing.
Preconditions#
- The manager has set a non-zero mint price on AccountSystem. In this PoC that price is
0.005 ether, the figure Gigaverse documents formintWithEth. mintUsernameis externally callable. The report describes it as the alternate mint path.- The username is not already taken.
Attack walkthrough#
Numbers from output.txt:
AccountSystem.mintPrice()is5000000000000000wei.- The GigaNameNFT table cell for the same column is
0. - The attacker calls
mintUsernamewithmsg.value == 0and usernamealice. - The require passes.
confirmMintsetsownerOf(1)to the attacker andethPaid(1)to0. - Protocol
feesCollectedstays0. The attacker holds one NAME and the stringalice.
The control test pays 5000000000000000 wei through mintWithEth, receives bob, and shows a second call with 0 value does not mint.
Diagrams#
Impact#
The protocol sells usernames for a manager-set ETH price and also exposes a mint that ignores that price. Every name taken through mintUsername is delivered without the fee. In the PoC the concrete loss is the 0.005 ETH fee on alice: the attacker receives token id 1, and both the name contract and AccountSystem keep 0 wei. Repeating the call takes every unused name for free. mintWithEth still charges, so the bug is the second entry point, not the price setter.
Remediation#
Make mintUsername read AccountSystem.mintPrice() — the same cell setMintPrice writes — or remove the public mint and leave minting to mintWithEth after that check.
Sources#
- AuditVault finding: https://github.com/Auditware/AuditVault/blob/main/findings/53288-h-05-zero-username-price-when-it-is-minted-with-mintusername.md
- Pashov report (2025-01-18): https://github.com/pashov/audits/blob/master/team/md/Gigaverse-security-review_2025-01-18.md
- The blamed
mintUsernamebody is the snippet in that report. Gigaverse did not publish the audited Solidity commit. The livemintWithEthprice of 0.005 ETH is documented at https://github.com/Gigaverse-Games/play/blob/main/references/onboarding.md for AccountSystem0x5f8b7eb615D5FCE81fafFb107450EdE201384C00on Abstract.
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 53288-h-05-zero-username-price-when-it-is-minted-with-mintusername_exp (evm-hack-registry mirror).
- AuditVault finding: 53288-h-05-zero-username-price-when-it-is-minted-with-mintusername.
Alerts & third-party analyses
These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.