feat(solana): implement SolanaIFTSendCallConstructor#959
Open
mariuszzak wants to merge 62 commits intomainfrom
Open
feat(solana): implement SolanaIFTSendCallConstructor#959mariuszzak wants to merge 62 commits intomainfrom
mariuszzak wants to merge 62 commits intomainfrom
Conversation
- Fix IFT mint/finalize_transfer account layouts for split app_state/app_mint_state - Add missing accounts to chunk uploads and recv_packet - Update e2e test for new IFT API signatures
- Replace relayer-controlled SolanaPayloadHint with constructor-hardcoded PDAs in SolanaIFTSendCallConstructor (6 immutables set at deployment) - Construct complete ABI-encoded GmpSolanaPayload on-chain in Ethereum, so Solana recv_packet decodes accounts directly from the IBC packet - Remove payload_hint instruction and state from ics27-gmp program - Simplify relayer payload_translator to extract accounts from ABI payload instead of building and storing hint transactions - Add DeployIFTContract.s.sol and DeploySolanaIFTConstructor.s.sol scripts - Add Test_EthSolana_IFT_TwoTokens E2E test verifying two independent tokens with separate bridges and constructors over the same client pair
- Define sol! struct types for GmpPacketData and GmpSolanaPayload - Replace hand-rolled read_offset/read_dynamic_bytes/encode_dynamic helpers with alloy's abi_decode/abi_encode - Add alloy-sol-types dependency to ics27-gmp
…ypes - Rename tx_builder/payload_translator.rs to tx_builder/gmp.rs - Move ABI_ENCODING and AbiGmpPacketData to src/gmp.rs alongside protobuf re-exports - Update all imports in ift.rs, packets.rs and tx_builder.rs
- Extract GMP accounts inline based on payload encoding instead of pre-extracting from the original IBC proto message - Remove AbiGmpAccountInfo wrapper, return Vec<AccountMeta> directly - Simplify extract_abi_gmp_accounts to a standalone function
- Single gmp module for all GMP logic (protobuf re-exports + ABI types + extraction) - Remove tx_builder/gmp.rs submodule
- Move ABI decoding into relayer-lib's solana_gmp module - extract_gmp_accounts now handles both protobuf and ABI encodings - Extract shared build_gmp_account_list for PDA derivation - eth-to-solana/gmp.rs reduced to re-exports - Callers use a single function regardless of encoding
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #959 +/- ##
=======================================
Coverage 99.91% 99.91%
=======================================
Files 27 28 +1
Lines 1123 1191 +68
=======================================
+ Hits 1122 1190 +68
Misses 1 1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
6 tasks
- Extract encoding-agnostic `gmp_packet_data::decode` from `abi.rs` - Add `From<AbiGmpPacketData>` and `TryFrom<AbiGmpSolanaPayload>` to reuse existing proto conversions - Add test coverage for protobuf path, invalid encoding and error cases
- Replace modulo check with chunks_exact().remainder() pattern
- Move ABI/protobuf dispatch for GmpSolanaPayload into gmp_solana_payload::decode - Remove inline encoding branching from on_recv_packet - Add unit tests for both encoding paths
- Add From<GmpPacketData> for AbiGmpPacketData conversion - Add gmp_packet_data::encode dispatching ABI vs protobuf - Remove encode_abi_gmp_packet helper, inline via From + abi_encode - Replace inline encoding branch in send_call with unified encode call
Adapt abi.rs and gmp_solana_payload.rs to the proto field rename from payer_position to prefund_lamports introduced in main.
The GMP program no longer injects a payer at a position index. Instead, the relayer pre-funds the GMP PDA before recv_packet. This commit: - Adds the payer account (GMP_ACCOUNT, signer+writable) at index 8 in the Solidity constructor and bumps NUM_ACCOUNTS from 11 to 12 - Replaces PAYER_POSITION=8 with PREFUND_LAMPORTS=3_000_000 - Adds extract_gmp_prefund_lamports to the shared relayer lib handling both protobuf and ABI encodings, consolidating duplicated decode logic - Wires a system_program::transfer pre-fund instruction into the eth-to-solana build_recv_packet_chunked flow - Moves MAX_PREFUND_LAMPORTS to the shared lib to eliminate duplication between cosmos-to-solana and eth-to-solana
…ransfer Align with #972 which removed ift_bridge from FinalizeTransfer accounts in the IFT program. The cosmos-to-solana relayer was updated but eth-to-solana was missed, causing AccountDiscriminatorMismatch (3002).
GetSolanaClockTime returns int64 but IftTransferMsg.TimeoutTimestamp expects uint64, causing compilation errors.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
SolanaIFTSendCallConstructor, a Solidity contract that builds the complete ABI-encodedGmpSolanaPayload(accounts + instruction data) on-chain at send time, eliminating the relayer-controlled hint account
eth-to-solanaandsolana-to-ethrelayer modules for bidirectional Ethereum-Solana relayics27-gmpwithalloy-sol-typessol!macroHow it works
The constructor stores 6 Solana PDAs as immutables at deploy time. When a user sends tokens,
constructMintCallpacks these PDAs together with the dynamic receiver (wallet + ATA) into the GmpSolanaPayload format that Solana's GMP program expects. The entire execution payload is committed in the IBC packet on Ethereum — the relayer just reads it and forwards it.Why the receiver encodes both wallet and ATA
On Solana, wallets don't hold SPL tokens directly. Each token balance lives in a dedicated Associated Token Account (ATA) — a PDA derived from the wallet address, the token program and the mint (the on-chain account that defines the token type, analogous to an ERC-20 contract address on Ethereum). The receiver field must include both the wallet and its ATA because the IFT mint instruction uses
init_if_neededto create the ATA if it doesn't exist yet.Creating or validating an ATA requires the wallet as the
authorityseed — the ATA address alone is not enough.Flow Diagram
graph TB subgraph Deployment["Deployment Phase"] direction LR D1[Derive 6 PDAs off-chain] --> D2[Deploy Constructor] D2 --> D3["Store as immutables:<br/>APP_STATE, APP_MINT_STATE,<br/>IFT_BRIDGE, MINT,<br/>MINT_AUTHORITY, GMP_ACCOUNT"] end U[User] subgraph Ethereum["Ethereum"] direction LR IFT[IFT Contract] -->|"2. constructMintCall(receiver, amount)"| CST[SolanaIFTSendCallConstructor] CST -->|"3. Pack 11 accounts<br/>(6 PDAs + wallet + ATA + 3 programs)"| PAYLOAD["4. abi.encode(packedAccounts,<br/>instructionData, payerPosition)"] PAYLOAD -->|return| IFT IFT -->|"5. sendPacket"| IBC[IBC Router] end subgraph Relayer["Relayer"] direction LR R1{"encoding?"} -->|"ABI<br/>(Ethereum)"| ABI["abi_decode →<br/>parse 34-byte<br/>packed accounts"] R1 -->|"Protobuf<br/>(Cosmos)"| PB["protobuf decode →<br/>native account structs"] ABI --> TX["Build recv_packet tx:<br/>14 ICS26 accounts +<br/>[gmp_pda, target_program,<br/>...execution_accounts]"] PB --> TX end subgraph Solana["Solana"] direction LR ROUTER["ICS26 Router"] -->|"CPI: on_recv_packet<br/>(dest_port = gmpport)"| GMP["ICS27 GMP Program"] GMP -->|"Decode payload,<br/>inject payer at payerPosition,<br/>CPI to target program"| IFTS[IFT Program] IFTS -->|Mint tokens| ATA[Receiver ATA] end Deployment -.-> Ethereum U -->|"1. iftTransfer(clientId, receiver, amount)"| IFT IBC --> Relayer TX -->|"recv_packet"| ROUTER style Deployment fill:#e8daef,stroke:#8e44ad,stroke-width:2px style Ethereum fill:#d4efdf,stroke:#27ae60,stroke-width:2px style Relayer fill:#fdebd0,stroke:#e67e22,stroke-width:2px style Solana fill:#d6eaf8,stroke:#2980b9,stroke-width:2px style U fill:#f9e79f,stroke:#f39c12,stroke-width:2px style IFT fill:#a9dfbf,stroke:#1e8449 style CST fill:#a9dfbf,stroke:#1e8449 style PAYLOAD fill:#a9dfbf,stroke:#1e8449 style IBC fill:#a9dfbf,stroke:#1e8449 style R1 fill:#fad7a0,stroke:#ca6f1e style ABI fill:#fad7a0,stroke:#ca6f1e style PB fill:#fad7a0,stroke:#ca6f1e style TX fill:#fad7a0,stroke:#ca6f1e style ROUTER fill:#aed6f1,stroke:#2471a3 style GMP fill:#aed6f1,stroke:#2471a3 style IFTS fill:#aed6f1,stroke:#2471a3 style ATA fill:#aed6f1,stroke:#2471a3 style D1 fill:#d2b4de,stroke:#7d3c98 style D2 fill:#d2b4de,stroke:#7d3c98 style D3 fill:#d2b4de,stroke:#7d3c98Key changes
contracts/utils/SolanaIFTSendCallConstructor.sol— constructor contract with 6 immutable PDAs, builds packedaccounts and Borsh-encoded instruction data using inline assembly
packages/relayer/modules/eth-to-solana/— new relayer module for Ethereum to Solana packet relay, includingABI payload decoding and Solana transaction building
packages/relayer/modules/solana-to-eth/— new relayer module for Solana to Ethereum relayprograms/solana/programs/ics27-gmp/src/abi.rs— replaced ~400 lines of manual ABI parsing withalloy-sol-typessol!macro typese2e/interchaintestv8/ethereum_solana_ift_test.go— E2E tests for single and two-token transfersscripts/DeploySolanaIFTConstructor.s.sol— forge deploy script that reads PDAs from env varsThis PR is based on #928 - proposes an alternative solution.