Reproduced Exploit

Ruyui — a shop purchase without signup mints 100 CREDIT for 0

purchaseShopItem assumes the caller already signed up. It does not check hasSignedUp. A caller who never signed up, and who pays nothing, still gets item 1 recorded and 100 CREDIT minted.

Jan 2026•Other•access-control•3 min read

Chain

Other

Category

access-control

Date

Jan 2026

Source

AuditVault

EVM Playground

Source-level debugger — step opcodes and Solidity in sync

evm-hack-analyzer

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.

Loading fork state…

Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 64703-h-01-it-is-possible-to-purchase-in-game-items-without-signin. Standalone Foundry PoC and full write-up: 64703-h-01-it-is-possible-to-purchase-in-game-items-without-signin_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/access-control/missing-check · vuln/logic/missing-validation

Reproduction: a self-contained Foundry PoC that compiles and runs offline with only forge-std — no fork and no RPC. Trace: output.txt. PoC: test/64703-h-01-it-is-possible-to-purchase-in-game-items-without-signin_exp.sol.


Key info#

ImpactHIGH — a caller who never signed up receives the item and 100 CREDIT for 0
ProtocolRoots of Embervault, Ruyui shop
Vulnerable codepurchaseShopItem updates userShopItemPurchases and mints credit with no hasSignedUp check
Bug classPurchase does not require signup
FindingShieldify — Roots of Embervault / Ruyui, January 2026 · id 64703, H-01
ReportRuyui security review
SourceAuditVault finding
StatusReproduced here as a local synthetic.
Compilersolc 0.8.27, cancun, via IR

TL;DR#

purchaseShopItem assumes the caller already signed up. It does not check hasSignedUp. A caller who never signed up, and who pays nothing, still gets item 1 recorded and 100 CREDIT minted.

Control requires hasSignedUp. The same purchase reverts. After signUp(), the purchase mints the 100 CREDIT.

The vulnerable code#

SOLIDITY
function purchaseShopItem(uint256 itemId) external {
    // @> VULN: no hasSignedUp check
    userShopItemPurchases[msg.sender][itemId] += 1;
    credit.mint(msg.sender, CREDIT_OUT);
}

The fix is require(hasSignedUp[msg.sender], "signup") before those writes.

Root cause#

Signup is what records the user and the referee. The purchase path updates per-user item state and pays credit as if that had already happened. Skipping it grants the item and the credit without the signup fee and without a referee.

Preconditions#

  • hasSignedUp for the caller is false.
  • The caller sends no value.

Attack walkthrough#

  1. Confirm the caller has not signed up.
  2. Call purchaseShopItem(1).
  3. The item counter becomes 1 and the shop mints 100 CREDIT.
  4. The caller's credit balance is 100. hasSignedUp is still false.

Control reverts with signup until signUp() is called. The signed-up purchase then mints 100 CREDIT.

Diagrams#

flowchart TD A["Caller has not signed up and pays 0"] --> B["purchaseShopItem writes the item counter"] B --> C["The shop mints 100 CREDIT"] C --> D["The caller holds the item and 100 CREDIT"] D --> E["hasSignedUp is still false"]

Impact#

The caller receives item 1 and 100 CREDIT without signing up and without paying. The referee who would have been set at signup is never paid.

Remediation#

Require hasSignedUp(msg.sender) at the start of purchaseShopItem and purchaseBattlePass.

Sources#

Taxonomy#

  • severity/high
  • sector/gaming
  • platform/shieldify

Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

  • Web3Sec X hacked database: search.
  • Rekt leaderboard: search.
  • Solodit incident search: search.

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.