Skip to content

fix: F-2026-18196 | [Dual Defense] Native SOL Misclassified as SPL Due to EVM-Only isNative Check - #307

Open
Aman035 wants to merge 4 commits into
audit-fixesfrom
F-2026-18196
Open

fix: F-2026-18196 | [Dual Defense] Native SOL Misclassified as SPL Due to EVM-Only isNative Check#307
Aman035 wants to merge 4 commits into
audit-fixesfrom
F-2026-18196

Conversation

@Aman035

@Aman035 Aman035 commented Aug 18, 2026

Copy link
Copy Markdown
Member

F-2026-18196 SVM builder native check missed Solana's native marker

Problem

  • The SVM builder decided native vs SPL with an EVM-only check: empty, 0x0, or the EVM zero hex.
  • It did not recognise Solana's native marker 11111111111111111111111111111111 (SystemProgram, the base58 zero pubkey).
  • When that value arrives, the builder takes the SPL path and runs CreateAssociatedTokenAccountIdempotent against SystemProgram, which is not a mint, so the transaction fails pre-broadcast.

What actually reaches us, confirmed by core dev and verified on donut

  • Core originally sent 11111111111111111111111111111111 for native, then changed to the EVM zero after discussion. Both forms are therefore live.
  • Withdrawals: core sends 0x0000000000000000000000000000000000000000. create_outbound.go copies the registry token address, and the registry has two pSOL rows sharing one PRC20, where the disabled EVM-zero row wins the lookup.
  • Reverts and rescues: core sends 11111111111111111111111111111111. build_revert_outbound.go copies inbound.AssetAddr, and the SVM parser sets that from the pubkey, so it is base58.
  • SPL mints are base58 in both directions, for example EiXDnrAg9ea2Q6vEPV7E5TpTU1vh41jcuZqKjU5Dc4ZF, and were never affected.

So withdrawals worked, which is why SDK tests stayed green. The revert and rescue path is the one that breaks.

What I did

  • Added one isNativeAsset(addr string) bool helper and used it at all three decision sites: GetOutboundSigningRequest, BuildOutboundTransaction, BuildRefRouteTransactions.
  • Downstream account builders already take isNative as a parameter, so rescue and revert inherit the fix.
  • Documented in the helper which encoding comes from which core path.
  • Pointed the buildAndSimulateRescue test helper at the same function. It had its own copy of the check, so tests were not exercising the production path.

Why it checks IsZero, not a string constant

  • base58ToHex("11111111111111111111111111111111") is 32 zero bytes, see event_parser_test.go.
  • Solana's native marker is the zero address, just base58 encoded.
  • So the helper parses the pubkey and checks IsZero(), covering SystemProgramID, the literal marker, and any other zero-pubkey spelling.
  • Hex input is also covered, since the builder accepts hex mints. Length is checked first because PublicKeyFromBytes panics on a short slice.

Tests

  • TestIsNativeAsset: core withdrawal form, core revert form, other zero spellings, real SPL mints (USDT.sol, USDC.sol, TokenProgramID) stay non-native, malformed input stays non-native.
  • TestNativeMarkerFormsBuildIdenticalAccounts: builds the account list from both marker forms and asserts they are byte identical, and that no recipient ATA appears. That is the failure the old check produced.
  • Both tests were re-run with the helper reverted to the old EVM-only behaviour and both fail, so they guard the fix rather than passing vacuously.

Worth flagging outside this PR

  • GetTokenConfigByPRC20 has no enabled filter and its comment assumes PRC20 addresses are unique, but two pSOL rows share one PRC20. Withdrawals currently work because the disabled row happens to sort first.
  • Deleting that stale row, which looks like safe cleanup, would make core send 11111111111111111111111111111111 for withdrawals too. This PR is what keeps that from becoming an outage.

@Aman035 Aman035 changed the title fix: recognize Solana native marker in SVM builder isNative check (F-2026-18196) fix: recognize Solana native marker in SVM builder native check (F-2026-18196) Aug 18, 2026
@Aman035 Aman035 changed the title fix: recognize Solana native marker in SVM builder native check (F-2026-18196) fix: F-2026-18196 | [Dual Defense] Native SOL Misclassified as SPL Due to EVM-Only isNative Check Aug 18, 2026
@Aman035

Aman035 commented Aug 18, 2026

Copy link
Copy Markdown
Member Author

@0xNilesh
Heads up on the Solana pSOL token config, found while fixing F-2026-18196 on the UV side.

There are two pSOL rows for solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1, both pointing at
PRC20 0x5D525Df2bD99a6e7ec58b76aF2fd95F39874EBed:

address: 11111111111111111111111111111111 enabled: true
address: 0x0000000000000000000000000000000000000000 no enabled field

GetTokenConfigByPRC20 matches on PRC20 + chain, returns the first hit, and does not
filter on enabled. The 0x0...0 row sorts first, so that is what every Solana outbound
currently carries. Verified on donut: all 7 pending Solana outbounds, and a 1500 utx
scan, show 0x0...0.

Two things worth a look:

  1. The row actually being used is the disabled one. Is that intended?
  2. The comment above that function says "PRC20 addresses are globally unique by
    construction; MatchExact returns at most one", which these two rows violate.

Separately, reverts take a different path: build_revert_outbound.go copies
inbound.AssetAddr, which is base58 from the UV parser. So the same asset goes out as
111...1 on reverts and 0x0...0 on withdrawals.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant