|
| 1 | +#![no_std] |
| 2 | + |
| 3 | +extern crate alloc; |
| 4 | +use alloc::vec::Vec; |
| 5 | + |
| 6 | +// Request type prefixes |
1 | 7 | const REQ_VAA_V1: &[u8; 4] = b"ERV1"; |
2 | 8 | const REQ_NTT_V1: &[u8; 4] = b"ERN1"; |
3 | 9 | const REQ_CCTP_V1: &[u8; 4] = b"ERC1"; |
@@ -63,6 +69,82 @@ pub fn make_cctp_v2_request() -> Vec<u8> { |
63 | 69 | out |
64 | 70 | } |
65 | 71 |
|
| 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 | + |
66 | 148 | #[cfg(test)] |
67 | 149 | mod tests { |
68 | 150 | use super::*; |
@@ -131,4 +213,63 @@ mod tests { |
131 | 213 | let result = make_cctp_v2_request(); |
132 | 214 | assert_eq!(result, [0x45, 0x52, 0x43, 0x32, 0x01]); |
133 | 215 | } |
| 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 | + } |
134 | 275 | } |
0 commit comments