Skip to content

Commit 6bb07fb

Browse files
vaporifsrdtrk
andauthored
feat(solana): add IFT program (#873)
Co-authored-by: srdtrk <[email protected]> Co-authored-by: srdtrk <[email protected]>
1 parent e3cdc17 commit 6bb07fb

File tree

162 files changed

+17427
-2215
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

162 files changed

+17427
-2215
lines changed

.github/workflows/solana.yml

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,6 @@ jobs:
5858
- name: Install just
5959
uses: extractions/setup-just@v2
6060

61-
- name: Install cargo-llvm-cov
62-
uses: taiki-e/install-action@cargo-llvm-cov
63-
6461
- name: Install protobuf compiler
6562
run: |
6663
sudo apt-get update
@@ -75,24 +72,6 @@ jobs:
7572
env:
7673
RUST_BACKTRACE: 1
7774

78-
# Exclude test programs
79-
- name: Run coverage
80-
run: |
81-
EXCLUDE='programs/(dummy-ibc-app'
82-
EXCLUDE+='|gmp-counter-app'
83-
EXCLUDE+='|malicious-caller'
84-
EXCLUDE+='|mock-ibc-app'
85-
EXCLUDE+='|mock-light-client)/'
86-
cargo llvm-cov --workspace --lcov --output-path lcov.info --ignore-filename-regex "$EXCLUDE"
87-
working-directory: programs/solana
88-
89-
- uses: codecov/codecov-action@v5
90-
with:
91-
files: programs/solana/lcov.info
92-
flags: solana
93-
env:
94-
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
95-
9675
- name: Run Solana linting
9776
run: just lint-solana
9877

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ members = [
3535
]
3636
resolver = "2"
3737

38+
[workspace.lints]
39+
3840
[workspace.package]
3941
version = "0.1.0"
4042
edition = "2021"

buf.gen.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ plugins:
1010
- local: protoc-gen-go-grpc
1111
out: e2e/interchaintestv8/types/
1212
opt: paths=source_relative
13+
# TODO: use go-proto in e2e rather than its own types
14+
- local: protoc-gen-go
15+
out: packages/go-proto/
16+
opt: paths=source_relative
1317

1418
inputs:
1519
- directory: proto

docs/adr/solana-ics27-gmp-architecture.md

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
**Status**: Implemented
44
**Date**: 2025-09-18
5-
**Last Updated**: 2025-11-07
5+
**Last Updated**: 2026-01-08
66

77
## Executive Summary
88

@@ -436,6 +436,72 @@ fn extract_payload_accounts(
436436
}
437437
```
438438

439+
## Packet Lifecycle Callbacks
440+
441+
### Problem
442+
443+
Sender programs (e.g., IFT) need to handle packet timeouts and acknowledgements for recovery (e.g., refund burned tokens).
444+
445+
### Solution: Pull-Based Result Storage
446+
447+
GMP stores results in `GMPCallResultAccount` PDAs. Sender programs read these accounts via their own claim instructions.
448+
449+
1. Router calls GMP's `on_timeout_packet` or `on_ack_packet`
450+
2. GMP creates `GMPCallResultAccount` PDA with the result
451+
3. Anyone calls sender's claim instruction (e.g., `IFT.claim_refund`)
452+
4. Sender reads GMP's result PDA and processes accordingly
453+
454+
### Alternatives Considered
455+
456+
**1. Push-based callback forwarding**: GMP forwards callbacks to sender programs via CPI. Rejected because it increases CPI depth and requires GMP to know sender interfaces.
457+
458+
**2. IFT registers as its own IBC app**: Rejected because it duplicates GMP's packet handling logic.
459+
460+
**3. Router calls sender directly**: Rejected because Router doesn't know GMP-specific packet encoding.
461+
462+
## CPI Authorization via PDA Signing
463+
464+
### The Problem
465+
466+
Solana's instruction sysvar only exposes the **top-level transaction instruction**, not the immediate CPI caller. For layered architectures like IFT → GMP → Router, we need a way for Router to verify that GMP (the registered IBC app) authorized the call.
467+
468+
### Solution: App Signer PDA
469+
470+
Instead of tracking CPI callers, Router validates that the registered app **signed** the request using its app state PDA:
471+
472+
```rust
473+
// Router's send_packet validation
474+
let (expected_app_signer, _) = Pubkey::find_program_address(
475+
&[b"ibc_app_state", ibc_app.port_id.as_bytes()],
476+
&ibc_app.app_program_id, // e.g., GMP program ID
477+
);
478+
require!(
479+
ctx.accounts.app_signer.key() == expected_app_signer,
480+
RouterError::UnauthorizedSender
481+
);
482+
```
483+
484+
### How It Works
485+
486+
1. **Registration**: GMP registers as the IBC app for "gmp" port
487+
2. **PDA Derivation**: GMP's app state PDA is `["ibc_app_state", "gmp"]` + GMP program ID
488+
3. **CPI Chain**: When IFT calls GMP's `send_call`, GMP signs with its app state PDA via `invoke_signed`
489+
4. **Validation**: Router verifies the signer matches the expected PDA for the registered app
490+
491+
This approach:
492+
- Works regardless of CPI depth (IFT → GMP → Router all works)
493+
- Only the registered app can sign with its PDA (cryptographic guarantee)
494+
- No need to track or whitelist upstream callers
495+
- Follows Solana's idiomatic PDA-based authorization pattern
496+
497+
### Alternatives Considered
498+
499+
**1. Upstream caller whitelisting**: Maintain a list of authorized upstream programs (e.g., IFT) in the `IBCApp` account. Router would accept calls if top-level program is in the whitelist. Rejected because it adds admin overhead, requires storage for the whitelist, and PDA signing is more elegant.
500+
501+
**2. Instruction sysvar inspection**: Walk the instruction sysvar to find the immediate CPI caller. Rejected because Solana's sysvar doesn't expose the CPI call stack, only top-level instructions.
502+
503+
**3. Pass "trusted" flag from GMP**: GMP validates its caller and passes a flag to Router. Rejected because it creates a security vulnerability—any program could pass the flag.
504+
439505
## Call Result Callbacks
440506

441507
When a GMP packet is acknowledged or times out, the GMP program creates a `GMPCallResultAccount` PDA to store the result:
@@ -461,6 +527,7 @@ See [Namespaced Sequence Calculation](solana-storage-architecture.md#namespaced-
461527

462528
**Relayer Integration**: The relayer computes and returns `gmp_result_pda` in `SolanaPacketTxs` for each ack/timeout packet, allowing callers to query results after relay.
463529

530+
464531
## Security Model
465532

466533
- **Account Control**: Only GMP program can sign via `invoke_signed` - users cannot directly control PDAs

0 commit comments

Comments
 (0)