Skip to content

Commit 869d2b0

Browse files
committed
executor quoter router added and TODO.md for tomorrow's work
1 parent 35abcfc commit 869d2b0

File tree

19 files changed

+3246
-13
lines changed

19 files changed

+3246
-13
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ node_modules/
22
lcov.info
33
.env
44
.env.*
5+
.devcontainer
6+
.claude

svm/Cargo.lock

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

svm/TODO.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# SVM Executor Programs - Remaining Tasks
2+
3+
## executor-quoter-router
4+
5+
### Events
6+
- [ ] Emit `QuoterContractUpdate` event in `update_quoter_contract.rs`
7+
- [ ] Emit `OnChainQuote` event in `request_execution.rs`
8+
9+
### Integration Tests
10+
- [ ] `test_quote_execution` - basic CPI to quoter
11+
- [ ] `test_request_execution` - full execution flow
12+
- [ ] `test_request_execution_underpaid` - payment validation
13+
- [ ] `test_request_execution_pays_payee` - payment routing
14+
- [ ] + Any remaining tests for full coverage if it had forge (just mirror tests in the evm impl)
15+
16+
## executor-quoter
17+
18+
### Features
19+
- [ ] Implement batch updates for executor-quoter program

svm/modules/executor-requests/src/lib.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
#![no_std]
2+
3+
extern crate alloc;
4+
use alloc::vec::Vec;
5+
6+
// Request type prefixes
17
const REQ_VAA_V1: &[u8; 4] = b"ERV1";
28
const REQ_NTT_V1: &[u8; 4] = b"ERN1";
39
const REQ_CCTP_V1: &[u8; 4] = b"ERC1";
@@ -63,6 +69,82 @@ pub fn make_cctp_v2_request() -> Vec<u8> {
6369
out
6470
}
6571

72+
// ============================================================================
73+
// Relay Instructions
74+
// ============================================================================
75+
//
76+
// Relay instructions tell the executor how to relay a message. The format
77+
// matches the Wormhole SDK `relayInstructionsLayout` from
78+
// @wormhole-foundation/sdk-definitions.
79+
//
80+
// Instructions are concatenated together. Each instruction starts with a
81+
// 1-byte type discriminator followed by type-specific data. All multi-byte
82+
// integers are big-endian.
83+
84+
/// Relay instruction type discriminators
85+
pub const RELAY_IX_GAS: u8 = 1;
86+
pub const RELAY_IX_GAS_DROP_OFF: u8 = 2;
87+
88+
/// Encodes a GasInstruction relay instruction.
89+
///
90+
/// Layout (33 bytes):
91+
/// - type: u8 = 1
92+
/// - gas_limit: u128 be (16 bytes)
93+
/// - msg_value: u128 be (16 bytes)
94+
pub fn make_relay_instruction_gas(gas_limit: u128, msg_value: u128) -> Vec<u8> {
95+
let mut out = Vec::with_capacity(33);
96+
out.push(RELAY_IX_GAS);
97+
out.extend_from_slice(&gas_limit.to_be_bytes());
98+
out.extend_from_slice(&msg_value.to_be_bytes());
99+
out
100+
}
101+
102+
/// Encodes a GasDropOffInstruction relay instruction.
103+
///
104+
/// Layout (49 bytes):
105+
/// - type: u8 = 2
106+
/// - drop_off: u128 be (16 bytes)
107+
/// - recipient: [u8; 32] (universal address)
108+
pub fn make_relay_instruction_gas_drop_off(drop_off: u128, recipient: &[u8; 32]) -> Vec<u8> {
109+
let mut out = Vec::with_capacity(49);
110+
out.push(RELAY_IX_GAS_DROP_OFF);
111+
out.extend_from_slice(&drop_off.to_be_bytes());
112+
out.extend_from_slice(recipient);
113+
out
114+
}
115+
116+
/// Builder for constructing relay instructions.
117+
///
118+
/// Multiple instructions can be combined by appending them together.
119+
/// This is a convenience wrapper that allows chaining.
120+
#[derive(Default)]
121+
pub struct RelayInstructionsBuilder {
122+
data: Vec<u8>,
123+
}
124+
125+
impl RelayInstructionsBuilder {
126+
pub fn new() -> Self {
127+
Self { data: Vec::new() }
128+
}
129+
130+
/// Add a GasInstruction to the relay instructions.
131+
pub fn with_gas(mut self, gas_limit: u128, msg_value: u128) -> Self {
132+
self.data.extend(make_relay_instruction_gas(gas_limit, msg_value));
133+
self
134+
}
135+
136+
/// Add a GasDropOffInstruction to the relay instructions.
137+
pub fn with_gas_drop_off(mut self, drop_off: u128, recipient: &[u8; 32]) -> Self {
138+
self.data.extend(make_relay_instruction_gas_drop_off(drop_off, recipient));
139+
self
140+
}
141+
142+
/// Build the final relay instructions bytes.
143+
pub fn build(self) -> Vec<u8> {
144+
self.data
145+
}
146+
}
147+
66148
#[cfg(test)]
67149
mod tests {
68150
use super::*;
@@ -131,4 +213,63 @@ mod tests {
131213
let result = make_cctp_v2_request();
132214
assert_eq!(result, [0x45, 0x52, 0x43, 0x32, 0x01]);
133215
}
216+
217+
#[test]
218+
fn test_relay_instruction_gas() {
219+
// GasInstruction with gasLimit=250_000 and msgValue=1_000_000
220+
let result = make_relay_instruction_gas(250_000, 1_000_000);
221+
assert_eq!(result.len(), 33);
222+
assert_eq!(result[0], RELAY_IX_GAS); // type = 1
223+
224+
// gas_limit: 250_000 = 0x3D090 as u128 big-endian (16 bytes)
225+
let expected_gas_limit: [u8; 16] = 250_000u128.to_be_bytes();
226+
assert_eq!(&result[1..17], &expected_gas_limit);
227+
228+
// msg_value: 1_000_000 = 0xF4240 as u128 big-endian (16 bytes)
229+
let expected_msg_value: [u8; 16] = 1_000_000u128.to_be_bytes();
230+
assert_eq!(&result[17..33], &expected_msg_value);
231+
}
232+
233+
#[test]
234+
fn test_relay_instruction_gas_drop_off() {
235+
let recipient = [0xAB; 32];
236+
let result = make_relay_instruction_gas_drop_off(500_000, &recipient);
237+
assert_eq!(result.len(), 49);
238+
assert_eq!(result[0], RELAY_IX_GAS_DROP_OFF); // type = 2
239+
240+
// drop_off: 500_000 as u128 big-endian (16 bytes)
241+
let expected_drop_off: [u8; 16] = 500_000u128.to_be_bytes();
242+
assert_eq!(&result[1..17], &expected_drop_off);
243+
244+
// recipient: 32 bytes
245+
assert_eq!(&result[17..49], &recipient);
246+
}
247+
248+
#[test]
249+
fn test_relay_instructions_builder() {
250+
let recipient = [0xCD; 32];
251+
let result = RelayInstructionsBuilder::new()
252+
.with_gas(100_000, 200_000)
253+
.with_gas_drop_off(300_000, &recipient)
254+
.build();
255+
256+
// Total: 33 + 49 = 82 bytes
257+
assert_eq!(result.len(), 82);
258+
259+
// First instruction: GasInstruction
260+
assert_eq!(result[0], RELAY_IX_GAS);
261+
assert_eq!(&result[1..17], &100_000u128.to_be_bytes());
262+
assert_eq!(&result[17..33], &200_000u128.to_be_bytes());
263+
264+
// Second instruction: GasDropOffInstruction
265+
assert_eq!(result[33], RELAY_IX_GAS_DROP_OFF);
266+
assert_eq!(&result[34..50], &300_000u128.to_be_bytes());
267+
assert_eq!(&result[50..82], &recipient);
268+
}
269+
270+
#[test]
271+
fn test_relay_instructions_builder_empty() {
272+
let result = RelayInstructionsBuilder::new().build();
273+
assert_eq!(result.len(), 0);
274+
}
134275
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "executor-quoter-router"
3+
version = "0.1.0"
4+
description = "Executor Quoter Router - routes execution requests to registered quoter implementations"
5+
edition = "2021"
6+
7+
[lib]
8+
crate-type = ["cdylib", "lib"]
9+
name = "executor_quoter_router"
10+
11+
[features]
12+
default = []
13+
no-entrypoint = []
14+
custom-heap = []
15+
custom-panic = []
16+
17+
[dependencies]
18+
pinocchio = "0.9.2"
19+
pinocchio-system = "0.4"
20+
bytemuck = { version = "1.14", features = ["derive"] }
21+
22+
[dev-dependencies]
23+
solana-program-test = "1.18"
24+
solana-sdk = "1.18"
25+
libsecp256k1 = "0.7"
26+
rand = "0.8"

0 commit comments

Comments
 (0)