Calculated from recorded token losses using historical USD prices at the incident time.
0x3ad998a01ad1f1bbe6dba6a08e658c1749dabfa4a07da20ded3c73bcd6970d200xef1f39d8391cddcaee62b8b383cb992f46a6ce4fBSC0x336a7675a863c12f7b49061b3ecb9e54be5e2120BSCOn BNB Chain block 39141427, transaction 0x3ad998a01ad1f1bbe6dba6a08e658c1749dabfa4a07da20ded3c73bcd6970d20 exploited P404's ERC20/ERC721 conversion logic. An adversary EOA 0xc468d9a3a5557bff457586438c130e3afbec2ff9 used helper contract 0xdaef6079cb84405dac688a9f6956c6830b7ddbc6 to burn third-party P404NFT token IDs, mint fungible P404 to itself, swap the minted P404 into WBNB on PancakeSwap, unwrap to BNB, and forward the proceeds to the EOA.
The root cause is an authorization failure across the token/NFT conversion boundary. P404Token._update() routes tokenId-sized transfers to transform() when to == address(this) without verifying that the caller owns or is approved for that NFT, and P404NFT.burn() accepts any burn request from the P404 token contract itself. Together, these two behaviors let any unprivileged caller destroy arbitrary public token IDs and redeem 9800 P404 per burned NFT.
P404 mixes ERC20 and ERC721 semantics in one system. Values below 30001 are interpreted as NFT token IDs, while larger values are treated as fungible token amounts. The intended NFT-to-token redemption path burns one NFT and mints 9800 P404, reflecting the protocol's configured 2% conversion loss from a 10000 P404 nominal unit.
Two contracts matter:
P404Token at decides whether a transfer should be handled as ERC20 movement or as NFT conversion.0xef1f39d8391cddcaee62b8b383cb992f46a6ce4fP404NFT at 0x336a7675a863c12f7b49061b3ecb9e54be5e2120 stores NFT ownership and trusts the configured p404contract to perform burns.The seed artifacts show that the PancakeSwap pair 0x0a86a9cc823f8febc1d26f1f880be3c986e7c042 held enough WBNB liquidity for the adversary to realize profit immediately after minting unauthorized P404.
This is an ATTACK-class ACT exploit caused by broken authorization in the NFT redemption path. The protocol invariant should be: only the owner of a P404NFT token, or an operator approved by that owner, may destroy that token and receive fungible redemption value. P404 violates that invariant in the dispatch branch that handles tokenId-sized values. When a caller executes P404Token.transfer(P404Token, tokenId), the overridden _update() function reaches transform(tokenId) before any NFT authorization check runs. transform() calls _erc721ToErc20(), which burns the NFT and mints 9800 P404 to msg.sender. The downstream burn succeeds because P404NFT.burn() only checks that msg.sender equals the configured token contract and does not validate the NFT owner or an approved operator. The result is a permissionless path for burning someone else's NFT and redirecting redemption proceeds to the attacker.
The relevant victim-side code from the collected verified sources is:
function _update(address from, address to, uint256 value) internal override {
if (!isValidTokenId(value)) {
super._update(from, to, value);
}
if (to == address(this) || to == erc721) {
transform(value);
} else {
if (isValidTokenId(value)) {
if (from != address(0)) {
require(
iERC721CheckAuth(erc721).isAuthorized(from, msg.sender, value),
"P404: not authorized"
);
}
IERC721(erc721).safeTransferFrom(from, to, value);
}
}
}
function _erc721ToErc20(uint256 _tokenId) internal {
IERC721Burnable(erc721).burn(_tokenId);
_mint(msg.sender, (TRANSFORM_PRICE * (10000 - TRANSFORM_LOSE_RATE)) / 10000);
}
Origin: verified P404Token source.
function burn(uint256 tokenId) external {
require(
p404contract == msg.sender,
"P404NFT: only p404contract can burn"
);
_withdrawAndStoreERC721(tokenId);
}
Origin: verified P404NFT source.
These two snippets are sufficient to explain the exploit:
value is treated as an NFT identifier.address(this) causes _update() to call transform(value).isAuthorized(from, msg.sender, value)._erc721ToErc20() burns the NFT and mints fungible P404 to the caller.P404NFT.burn() accepts the request because the caller is the trusted token contract, not because the real NFT owner approved it.The on-chain trace confirms this exact execution path. The seed trace contains repeated entries such as:
P404Token::transfer(P404Token, 2)
P404NFT::burn(2)
emit Transfer(..., attacker, 9800000000000000000000)
Origin: seed trace for tx 0x3ad998a01ad1f1bbe6dba6a08e658c1749dabfa4a07da20ded3c73bcd6970d20.
The balance diff corroborates that unrelated NFT holders lost balances while the adversary gained proceeds. For example, holder 0x0f143ae118e38cd5218f1a1ada9b793053c6d679 lost four P404NFT balance units in the transaction-level diff, while the adversary EOA gained 64745879478674455403 wei net of the helper payout flow and before subtracting gas the helper realized 64820147313674455403 WBNB-equivalent output.
The adversary flow is fully contained in one transaction:
0xc468d9a3a5557bff457586438c130e3afbec2ff9 calls helper contract 0xdaef6079cb84405dac688a9f6956c6830b7ddbc6.P404Token.transfer(P404Token, tokenId).P404NFT.burn(tokenId) succeeds and 9800 P404 is minted to the helper.0x10ed43c718714eb63d5aa57b78b54704e256024e.P404/WBNB pair and receives WBNB.Representative trace evidence:
P404Token::approve(router, max)
swapExactTokensForTokensSupportingFeeOnTransferTokens(... path=[P404,WBNB] ...)
WBNB::withdraw(64820147313674455403)
Origin: seed trace for the exploit transaction.
This matches the ACT framing in root_cause.json: any unprivileged actor could deploy a similar helper, select existing public token IDs, and execute the same redemption and liquidation sequence against the pre-state immediately before block 39141427.
The attack burned 374 NFTs held by unrelated P404NFT holders and extracted liquidity from the PancakeSwap pool. The measured protocol-side token loss recorded in the analysis is:
WBNB: 64820147313674455403 raw units (64.820147313674455403 WBNB)The adversary EOA's net result after transaction fees was a positive 64.671611643674455403 BNB, based on:
3.036430852789792412 BNB67.782310331464247815 BNB0.074267835 BNBThe economic damage therefore affected both NFT holders, whose tokens were destroyed without consent, and the liquidity pool that paid out the WBNB used to realize the attacker's profit.
0x3ad998a01ad1f1bbe6dba6a08e658c1749dabfa4a07da20ded3c73bcd6970d200xc468d9a3a5557bff457586438c130e3afbec2ff90xdaef6079cb84405dac688a9f6956c6830b7ddbc60xef1f39d8391cddcaee62b8b383cb992f46a6ce4f0x336a7675a863c12f7b49061b3ecb9e54be5e21200xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c0x10ed43c718714eb63d5aa57b78b54704e256024e0x0a86a9cc823f8febc1d26f1f880be3c986e7c042P404Token source, verified P404NFT source