Skip to content

Commit fba0d11

Browse files
committed
test: added fallback routing tests
1 parent 5394a72 commit fba0d11

4 files changed

Lines changed: 1127 additions & 39 deletions

File tree

rust/sealevel/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.

rust/sealevel/client/README.md

Lines changed: 71 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ hyperlane-sealevel-client composite-ism transfer-ownership \
6363

6464
### Config File Format
6565

66-
The config file is a JSON file representing the root `IsmNode`. The `"type"` field selects the ISM variant; all field names are camelCase.
66+
The config file is a JSON file representing the root `IsmNode`. The `"type"` field selects the ISM variant. Variant names are camelCase; field names within variants are snake_case (matching the Rust struct fields exactly).
6767

6868
#### Leaf nodes
6969

@@ -75,17 +75,12 @@ The config file is a JSON file representing the root `IsmNode`. The `"type"` fie
7575
}
7676
```
7777

78-
**`multisigMessageId`** — ECDSA threshold multisig over `CheckpointWithMessageId`, one validator set per origin domain.
78+
**`multisigMessageId`** — ECDSA threshold multisig over `CheckpointWithMessageId`. Flat validators/threshold for a single validator set; use a `routing` or `fallbackRouting` parent to select different sets per origin domain.
7979
```json
8080
{
8181
"type": "multisigMessageId",
82-
"domainConfigs": [
83-
{
84-
"origin": 1000,
85-
"validators": ["0xabc123...", "0xdef456..."],
86-
"threshold": 2
87-
}
88-
]
82+
"validators": ["0xabc123...", "0xdef456..."],
83+
"threshold": 2
8984
}
9085
```
9186

@@ -99,70 +94,107 @@ The config file is a JSON file representing the root `IsmNode`. The `"type"` fie
9994
{ "type": "pausable", "paused": false }
10095
```
10196

97+
**`rateLimited`** — token-bucket rate limiter. Rejects messages once the bucket is empty; the bucket refills over time up to `max_capacity`. `recipient` is an optional H256 address used to restrict which message recipient this node applies to.
98+
```json
99+
{
100+
"type": "rateLimited",
101+
"max_capacity": 10000,
102+
"recipient": "0x000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
103+
}
104+
```
105+
106+
> `recipient` is optional and may be omitted. Mutable state (`filled_level`, `last_updated`) is managed on-chain and is never part of the config file.
107+
102108
#### Compound nodes
103109

104110
**`aggregation`** — m-of-n: requires metadata from at least `threshold` sub-ISMs, and all sub-ISMs with provided metadata must verify.
105111
```json
106112
{
107113
"type": "aggregation",
108114
"threshold": 2,
109-
"subIsms": [
115+
"sub_isms": [
110116
{ "type": "trustedRelayer", "relayer": "<Pubkey>" },
111117
{ "type": "test", "accept": true },
112-
{ "type": "multisigMessageId", "domainConfigs": [...] }
118+
{ "type": "multisigMessageId", "validators": ["0x..."], "threshold": 1 }
113119
]
114120
}
115121
```
116122

117-
**`routing`** — routes to a sub-ISM based on the message's origin domain. Falls back to `defaultIsm` if the origin has no explicit route.
123+
**`routing`** — routes to a per-domain sub-ISM based on the message's origin domain. Returns an error if no ISM is configured for the incoming origin. Use `fallbackRouting` if you need a catch-all.
124+
125+
The `domains` object maps decimal domain ID strings to ISM configs. During `deploy`/`update` each entry is submitted as a separate `SetDomainIsm` transaction.
118126
```json
119127
{
120128
"type": "routing",
121-
"routes": [
122-
{ "domain": 1000, "ism": { "type": "trustedRelayer", "relayer": "<Pubkey>" } },
123-
{ "domain": 2000, "ism": { "type": "test", "accept": false } }
124-
],
125-
"defaultIsm": { "type": "pausable", "paused": false }
129+
"domains": {
130+
"1000": { "type": "trustedRelayer", "relayer": "<Pubkey>" },
131+
"2000": {
132+
"type": "multisigMessageId",
133+
"validators": ["0xabc..."],
134+
"threshold": 1
135+
}
136+
}
126137
}
127138
```
128139

129-
**`amountRouting`** — routes based on the token amount in `body[32..64]` (big-endian u256, TokenMessage format). Routes to `upper` if `amount >= threshold`, else `lower`.
140+
**`fallbackRouting`** — like `routing`, but falls back to a statically-configured ISM program (`fallback_ism`) when the message's origin domain has no explicit override. The fallback ISM must be another deployed composite ISM program.
141+
```json
142+
{
143+
"type": "fallbackRouting",
144+
"fallback_ism": "<base58 Pubkey of fallback composite ISM program>",
145+
"domains": {
146+
"1000": { "type": "trustedRelayer", "relayer": "<Pubkey>" }
147+
}
148+
}
149+
```
150+
151+
> `domains` is optional (omitted when empty). When no per-domain ISM matches, the fallback ISM's `VerifyMetadataSpec` / `VerifyAccountMetas` / `Verify` are called via CPI.
152+
153+
**`amountRouting`** — routes based on the token transfer amount in `body[32..64]` (big-endian u256, TokenMessage format). Routes to `upper` if `amount >= threshold`, else `lower`. `threshold` is a decimal string representing the u256.
130154
```json
131155
{
132156
"type": "amountRouting",
133-
"threshold": "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000",
157+
"threshold": "20000000000000000",
134158
"lower": { "type": "trustedRelayer", "relayer": "<Pubkey>" },
135-
"upper": { "type": "multisigMessageId", "domainConfigs": [...] }
159+
"upper": {
160+
"type": "multisigMessageId",
161+
"validators": ["0xabc..."],
162+
"threshold": 1
163+
}
136164
}
137165
```
138166

139-
> The `threshold` is a `"0x"`-prefixed 64-character hex string representing a 32-byte big-endian u256 (e.g. `1e18` = `0x0000...000de0b6b3a7640000`).
167+
> `threshold` is a plain decimal integer string (e.g. `"20000000000000000"` for 0.02 ETH in wei units). The value is a big-endian u256.
140168
141169
#### Full example
142170

171+
A `fallbackRouting` ISM that uses a per-domain multisig for origin 1000 and falls back to a trusted-relayer composite ISM for all other origins:
172+
173+
```json
174+
{
175+
"type": "fallbackRouting",
176+
"fallback_ism": "4Nd1mBQtrMJVYVfKf2PX98YCKvsyfDXxsY7E3D4siqYb",
177+
"domains": {
178+
"1000": {
179+
"type": "multisigMessageId",
180+
"validators": ["0xabcdef1234567890abcdef1234567890abcdef12"],
181+
"threshold": 1
182+
}
183+
}
184+
}
185+
```
186+
187+
A 1-of-2 aggregation where one branch is a multisig and the other is a trusted relayer:
188+
143189
```json
144190
{
145191
"type": "aggregation",
146192
"threshold": 1,
147-
"subIsms": [
193+
"sub_isms": [
148194
{
149-
"type": "routing",
150-
"routes": [
151-
{
152-
"domain": 1000,
153-
"ism": {
154-
"type": "multisigMessageId",
155-
"domainConfigs": [
156-
{
157-
"origin": 1000,
158-
"validators": ["0xabcdef1234567890abcdef1234567890abcdef12"],
159-
"threshold": 1
160-
}
161-
]
162-
}
163-
}
164-
],
165-
"defaultIsm": { "type": "test", "accept": false }
195+
"type": "multisigMessageId",
196+
"validators": ["0xabcdef1234567890abcdef1234567890abcdef12"],
197+
"threshold": 1
166198
},
167199
{
168200
"type": "trustedRelayer",

rust/sealevel/programs/ism/composite-ism/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,16 @@ serializable-account-meta = { path = "../../../libraries/serializable-account-me
2828

2929
[dev-dependencies]
3030
hyperlane-test-utils = { path = "../../../libraries/test-utils" }
31+
hyperlane-sealevel-test-ism = { path = "../test-ism" }
32+
hyperlane-sealevel-multisig-ism-message-id = { path = "../multisig-ism-message-id", features = ["no-entrypoint"] }
33+
hyperlane-sealevel-test-send-receiver = { path = "../../test-send-receiver", features = ["test-client"] }
34+
ecdsa-signature = { path = "../../../libraries/ecdsa-signature" }
35+
multisig-ism = { path = "../../../libraries/multisig-ism" }
3136
solana-banks-interface.workspace = true
3237
tokio = { workspace = true, features = ["macros", "rt"] }
3338
solana-program-test.workspace = true
3439
solana-sdk.workspace = true
40+
spl-noop.workspace = true
3541
hex.workspace = true
3642
rand = "0.8.5"
3743

0 commit comments

Comments
 (0)