Skip to content

feat(near): added sui support in mpc prover - #647

Open
frolvanya wants to merge 1 commit into
feat/suifrom
feat/sui-mpc-prover
Open

feat(near): added sui support in mpc prover#647
frolvanya wants to merge 1 commit into
feat/suifrom
feat/sui-mpc-prover

Conversation

@frolvanya

Copy link
Copy Markdown
Contributor

Summary

Adds the NEAR-side prover path for Sui, so InitTransfer / FinTransfer /
DeployToken / LogMetadata events emitted by the Sui locker can be proven
back to NEAR. This was the last code blocker listed in sui/README.md;
everything remaining after this is DAO calls and deployment.

Mirrors the Aptos rollout.

What's included

  • near/omni-types/src/sui/events.rs — parsers for the four bridge
    events, plus parse_sui_event / parse_sui_proof dispatch.
  • near/omni-types/src/sui/bcs.rs — minimal BCS reader (no new deps).
  • MpcFinality::Sui(SuiFinality::Checkpointed) and the ChainKind::Sui
    arm in mpc-omni-prover (request_to_chain_kind,
    request_matches_finality, verify_callback, seeded in init).
  • near-mpc-sdk rev bump dcc9e042 -> 70c40b0f (merge commit of
    feat(foreign-tx): sui support in contract interface near/mpc#3754, which adds the Sui contract-interface DTOs). The old pin has
    no Sui types at all. Verified no breaking changes for EVM / Starknet /
    Aptos.

Two things worth reviewing closely

1. BCS, not JSON. Aptos delivers AptosEvent.data as a JSON rendering;
Sui delivers SuiEvent.bcs as canonical BCS bytes. Parsing is therefore
positional and must match the Move struct declaration field-for-field — note
the Sui events carry an extra coin_type: String between token_address and
the numeric fields, which Aptos does not have.

Two guards make a layout mistake loud instead of silent:

  • Reader::finish() rejects trailing bytes, so a wrong field order fails
    rather than producing a plausible-looking wrong value;
  • ULEB128 lengths are canonical-only (BCS uses ULEB128 where Borsh uses a
    fixed u32 — easy to get wrong).

2. Emitter comes from type_tag, not event.package_id. A Move type
keeps its defining package id across package upgrades, while package_id
becomes the upgraded package's id. Only the former stays equal to the factory
address registered on NEAR, so this survives a bridge package upgrade. Same
rule as the Aptos type-tag-address convention.

One extra check Aptos can't do

Sui coins are types, so every event carries both the 32-byte wire id and the
coin_type string it derives from. The parsers assert
keccak256(coin_type) == token_address, binding the two representations.

Tests

113 passing (+25): 77 in omni-types, 36 in mpc-omni-prover.

BCS fixtures are generated by an independent Python reference encoder, so the
tests don't just assert the Rust against itself. Coverage: all four events,
tag dispatch, proof-kind/tag mismatch, the keccak binding (flip one byte of
the token id), trailing and truncated payloads, short-form addresses,
malformed tags, non-canonical ULEB128, non-UTF-8 strings, and the
prover-level dispatch/finality/roundtrip cases mirroring Aptos.

Verified: cargo test, make clippy-near (pedantic, -D warnings),
make fmt-near, and cargo check --target wasm32-unknown-unknown.

@frolvanya

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Pull request overview

Adds the NEAR-side MPC-prover path for Sui, so InitTransfer / FinTransfer / DeployToken / LogMetadata events emitted by the Sui locker can be proven back to NEAR. This is the Sui analogue of the Aptos rollout (#626 / #629) and is stacked on feat/sui (which already carries ChainKind::Sui / OmniAddress::Sui). The one structural difference from Aptos is that the MPC layer delivers Sui events as canonical BCS bytes (SuiEvent.bcs) rather than a JSON rendering, so parsing is positional and must mirror the Move #[event] structs field-for-field.

Changes:

  • New near/omni-types/src/sui/{bcs.rs,events.rs,mod.rs}: a minimal positional BCS reader and four event parsers + parse_sui_event / parse_sui_proof dispatch.
  • mpc-omni-prover: MpcFinality::Sui(SuiFinality::Checkpointed) seeded in init, plus ChainKind::Sui arms in request_to_chain_kind, request_matches_finality, and the result-parse dispatch (parse_sui_result).
  • MpcFinality::Sui appended to the enum in mpc_types.rs.
  • near-mpc-sdk git rev bump dcc9e04270c40b0f (adds the Sui contract-interface DTOs).
  • Doc updates (near/CLAUDE.md, sui/README.md).

Reviewed changes

Per-file summary
File Description
near/omni-types/src/sui/bcs.rs New positional BCS reader (u8/u64/u128, address, vector, String, Option, ULEB128) with trailing-byte / canonical-ULEB / UTF-8 guards
near/omni-types/src/sui/events.rs Four BCS event parsers, emitter-from-type_tag derivation, keccak256(coin_type)==token_address binding, ProofKind/type_tag dispatch
near/omni-types/src/sui/mod.rs Module wiring
near/omni-types/src/lib.rs pub mod sui;
near/omni-types/src/mpc_types.rs MpcFinality::Sui(SuiFinality) variant appended
near/omni-prover/mpc-omni-prover/src/lib.rs Sui finality seed + ChainKind::Sui dispatch to parse_sui_result
near/omni-prover/mpc-omni-prover/src/tests.rs Sui prover-level tests (dispatch, finality, roundtrip, rejects)
near/Cargo.toml / Cargo.lock near-mpc-sdk rev bump
near/CLAUDE.md, sui/README.md Docs

Findings

Blocking: none identified.

I traced all four BCS test fixtures byte-for-byte and each decodes exactly as the parser reads it and as the doc-comment layout claims (e.g. InitTransfer = sender(32) | token(32) | uleb(0x4e=78) coin_type | nonce=7 LE | amount=1000 | fee=10 | native_fee=5 | uleb(0x13) "near:frolik.testnet" | uleb(0x00) message). The guards are sound: finish() rejects trailing bytes, uleb128 rejects non-canonical/over-u32 lengths, strings enforce UTF-8, Option rejects tags >1, origin_chain goes through TryFrom<u8>, and every parser both checks the type_tag suffix and binds keccak256(coin_type) == token_address. Prover wiring mirrors Aptos completely (init map, request_to_chain_kind, request_matches_finality, parse dispatch), and appending MpcFinality::Sui is borsh-safe (no existing discriminant renumbered).

Non-blocking (verify before merge — no automated gate covers this):

  • near/omni-types/src/sui/events.rs:653,694,726,753The positional BCS layouts cannot be verified against the source of truth in this review. The actual Sui Move #[event] structs live in sui/sources/omni_bridge.move on the base branch feat/sui, which isn't in this diff, and per §8 there is no Sui CI workflow and no cross-chain payload gate — the round-trip tests use fixtures from an independent Python encoder, so they prove the parser agrees with that encoder, not with the Move contract. A single mis-ordered/mis-typed field would compile, pass all tests here, and silently brick the Sui lane. Please diff each parser's field order/types against the Move #[event] declarations one field at a time before merging.
  • near/omni-types/src/sui/events.rs:704 — Two specific spots to confirm against the Move source, since they differ from the Aptos mirror: (1) coin_type: String is read immediately after token_address in all four events (the new field), and (2) FinTransfer.message is read as a plain vector<u8> here, whereas Aptos declares it Option<vector<u8>> (aptos/events.rs:191). If the Sui struct actually uses Option<vector<u8>>, a Some(..) message would misalign and fail finish() (a None/empty message would still pass), so this only surfaces on a message-bearing FinTransfer — worth an explicit confirmation.

✅ Approved

@frolvanya
frolvanya marked this pull request as ready for review August 21, 2026 11:22
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