Skip to content

Commit fdd4f90

Browse files
kcsongorevan-gray
authored andcommitted
solana: add recipient manager to endpoint message
(this follows #162 on the EVM side)
1 parent ad3b1da commit fdd4f90

File tree

12 files changed

+139
-22
lines changed

12 files changed

+139
-22
lines changed

solana/modules/ntt-messages/src/endpoint.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub trait Endpoint {
1919
)]
2020
pub struct EndpointMessageData<A: MaybeSpace> {
2121
pub source_manager: [u8; 32],
22+
pub recipient_manager: [u8; 32],
2223
pub manager_payload: ManagerMessage<A>,
2324
}
2425

@@ -70,13 +71,15 @@ where
7071
{
7172
pub fn new(
7273
source_manager: [u8; 32],
74+
recipient_manager: [u8; 32],
7375
manager_payload: ManagerMessage<A>,
7476
endpoint_payload: Vec<u8>,
7577
) -> Self {
7678
Self {
7779
_phantom: PhantomData,
7880
message_data: EndpointMessageData {
7981
source_manager,
82+
recipient_manager,
8083
manager_payload,
8184
},
8285
endpoint_payload,
@@ -112,6 +115,7 @@ where
112115
}
113116

114117
let source_manager = Readable::read(reader)?;
118+
let recipient_manager = Readable::read(reader)?;
115119
// TODO: we need a way to easily check that decoding the payload
116120
// consumes the expected amount of bytes
117121
let _manager_payload_len: u16 = Readable::read(reader)?;
@@ -122,6 +126,7 @@ where
122126

123127
Ok(EndpointMessage::new(
124128
source_manager,
129+
recipient_manager,
125130
manager_payload,
126131
endpoint_payload,
127132
))
@@ -148,13 +153,15 @@ where
148153
message_data:
149154
EndpointMessageData {
150155
source_manager,
156+
recipient_manager,
151157
manager_payload,
152158
},
153159
endpoint_payload,
154160
} = self;
155161

156162
E::PREFIX.write(writer)?;
157163
source_manager.write(writer)?;
164+
recipient_manager.write(writer)?;
158165
let len: u16 = u16::try_from(manager_payload.written_size()).expect("u16 overflow");
159166
len.write(writer)?;
160167
// TODO: review this in wormhole-io. The written_size logic is error prone. Instead,
@@ -180,7 +187,7 @@ mod test {
180187
//
181188
#[test]
182189
fn test_deserialize_endpoint_message() {
183-
let data = hex::decode("9945ff10042942fafabe00000000000000000000000000000000000000000000000000000079000000367999a1014667921341234300000000000000000000000000000000000000000000000000004f994e545407000000000012d687beefface00000000000000000000000000000000000000000000000000000000feebcafe0000000000000000000000000000000000000000000000000000000000110000").unwrap();
190+
let data = hex::decode("9945ff10042942fafabe0000000000000000000000000000000000000000000000000000042942fababe00000000000000000000000000000000000000000000000000000079000000367999a1014667921341234300000000000000000000000000000000000000000000000000004f994e545407000000000012d687beefface00000000000000000000000000000000000000000000000000000000feebcafe0000000000000000000000000000000000000000000000000000000000110000").unwrap();
184191
let mut vec = &data[..];
185192
let message: EndpointMessage<WormholeEndpoint, NativeTokenTransfer> =
186193
TypePrefixedPayload::read_payload(&mut vec).unwrap();
@@ -192,6 +199,10 @@ mod test {
192199
0x04, 0x29, 0x42, 0xFA, 0xFA, 0xBE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
193200
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
194201
],
202+
recipient_manager: [
203+
0x04, 0x29, 0x42, 0xFA, 0xBA, 0xBE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
204+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
205+
],
195206
manager_payload: ManagerMessage {
196207
sequence: 233968345345,
197208
sender: [

solana/programs/example-native-token-transfers/src/endpoints/wormhole/instructions/release_outbound.rs

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ pub fn release_outbound(ctx: Context<ReleaseOutbound>, args: ReleaseOutboundArgs
9090
let message: EndpointMessage<WormholeEndpoint, NativeTokenTransfer> = EndpointMessage::new(
9191
// TODO: should we just put the ntt id here statically?
9292
accs.outbox_item.to_account_info().owner.to_bytes(),
93+
accs.outbox_item.recipient_manager,
9394
ManagerMessage {
9495
sequence: accs.outbox_item.sequence,
9596
sender: accs.outbox_item.sender.to_bytes(),

solana/programs/example-native-token-transfers/src/error.rs

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ pub enum NTTError {
1515
InvalidEndpointSibling,
1616
#[msg("InvalidManagerSibling")]
1717
InvalidManagerSibling,
18+
#[msg("InvalidRecipientManager")]
19+
InvalidRecipientManager,
1820
#[msg("TransferAlreadyRedeemed")]
1921
TransferAlreadyRedeemed,
2022
#[msg("TransferNotApproved")]

solana/programs/example-native-token-transfers/src/instructions/redeem.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ pub struct Redeem<'info> {
3232
pub sibling: Account<'info, ManagerSibling>,
3333

3434
#[account(
35-
// check that the messages is targeted to this chain
35+
// check that the message is targeted to this chain
3636
constraint = endpoint_message.message.manager_payload.payload.to_chain == config.chain_id @ NTTError::InvalidChainId,
37+
// check that we're the intended recipient
38+
constraint = endpoint_message.message.recipient_manager == crate::ID.to_bytes() @ NTTError::InvalidRecipientManager,
3739
// NOTE: we don't replay protect VAAs. Instead, we replay protect
3840
// executing the messages themselves with the [`released`] flag.
3941
owner = endpoint.endpoint_address,

solana/programs/example-native-token-transfers/src/instructions/transfer.rs

+23-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::{
1111
outbox::{OutboxItem, OutboxRateLimit},
1212
rate_limit::RateLimitResult,
1313
},
14+
sibling::ManagerSibling,
1415
};
1516

1617
// this will burn the funds and create an account that either allows sending the
@@ -88,9 +89,15 @@ pub struct TransferBurn<'info> {
8889
seeds = [InboxRateLimit::SEED_PREFIX, args.recipient_chain.id.to_be_bytes().as_ref()],
8990
bump = inbox_rate_limit.bump,
9091
)]
91-
// NOTE: it would be nice to put this into `common`, but that way we don't
92+
// NOTE: it would be nice to put these into `common`, but that way we don't
9293
// have access to the instruction args
9394
pub inbox_rate_limit: Account<'info, InboxRateLimit>,
95+
96+
#[account(
97+
seeds = [ManagerSibling::SEED_PREFIX, args.recipient_chain.id.to_be_bytes().as_ref()],
98+
bump = sibling.bump,
99+
)]
100+
pub sibling: Account<'info, ManagerSibling>,
94101
}
95102

96103
// TODO: fees for relaying?
@@ -128,11 +135,14 @@ pub fn transfer_burn(ctx: Context<TransferBurn>, args: TransferArgs) -> Result<(
128135
amount,
129136
)?;
130137

138+
let recipient_manager = accs.sibling.address;
139+
131140
insert_into_outbox(
132141
&mut accs.common,
133142
&mut accs.inbox_rate_limit,
134143
amount,
135144
recipient_chain,
145+
recipient_manager,
136146
recipient_address,
137147
should_queue,
138148
)
@@ -150,10 +160,16 @@ pub struct TransferLock<'info> {
150160
seeds = [InboxRateLimit::SEED_PREFIX, args.recipient_chain.id.to_be_bytes().as_ref()],
151161
bump = inbox_rate_limit.bump,
152162
)]
153-
// NOTE: it would be nice to put this into `common`, but that way we don't
163+
// NOTE: it would be nice to put these into `common`, but that way we don't
154164
// have access to the instruction args
155165
pub inbox_rate_limit: Account<'info, InboxRateLimit>,
156166

167+
#[account(
168+
seeds = [ManagerSibling::SEED_PREFIX, args.recipient_chain.id.to_be_bytes().as_ref()],
169+
bump = sibling.bump,
170+
)]
171+
pub sibling: Account<'info, ManagerSibling>,
172+
157173
#[account(
158174
mut,
159175
token::mint = common.mint,
@@ -200,11 +216,14 @@ pub fn transfer_lock(ctx: Context<TransferLock>, args: TransferArgs) -> Result<(
200216
accs.common.mint.decimals,
201217
)?;
202218

219+
let recipient_manager = accs.sibling.address;
220+
203221
insert_into_outbox(
204222
&mut accs.common,
205223
&mut accs.inbox_rate_limit,
206224
amount,
207225
recipient_chain,
226+
recipient_manager,
208227
recipient_address,
209228
should_queue,
210229
)
@@ -215,6 +234,7 @@ fn insert_into_outbox(
215234
inbox_rate_limit: &mut InboxRateLimit,
216235
amount: u64,
217236
recipient_chain: ChainId,
237+
recipient_manager: [u8; 32],
218238
recipient_address: [u8; 32],
219239
should_queue: bool,
220240
) -> Result<()> {
@@ -241,6 +261,7 @@ fn insert_into_outbox(
241261
amount: NormalizedAmount::normalize(amount, common.mint.decimals),
242262
sender: common.sender.key(),
243263
recipient_chain,
264+
recipient_manager,
244265
recipient_address,
245266
release_timestamp,
246267
released: Bitmap::new(),

solana/programs/example-native-token-transfers/src/queue/outbox.rs

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub struct OutboxItem {
1515
pub amount: NormalizedAmount,
1616
pub sender: Pubkey,
1717
pub recipient_chain: ChainId,
18+
pub recipient_manager: [u8; 32],
1819
pub recipient_address: [u8; 32],
1920
pub release_timestamp: i64,
2021
pub released: Bitmap,

solana/programs/example-native-token-transfers/tests/cancel_flow.rs

+61-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use anchor_lang::prelude::*;
55
use common::setup::{TestData, OTHER_CHAIN, OTHER_ENDPOINT, OTHER_MANAGER, THIS_CHAIN};
66
use example_native_token_transfers::{
77
config::Mode,
8+
error::NTTError,
89
instructions::{RedeemArgs, TransferArgs},
910
queue::{inbox::InboxRateLimit, outbox::OutboxRateLimit},
1011
};
@@ -13,8 +14,9 @@ use ntt_messages::{
1314
manager::ManagerMessage, normalized_amount::NormalizedAmount, ntt::NativeTokenTransfer,
1415
};
1516
use sdk::endpoints::wormhole::instructions::receive_message::ReceiveMessage;
17+
use solana_program::instruction::InstructionError;
1618
use solana_program_test::*;
17-
use solana_sdk::{signature::Keypair, signer::Signer};
19+
use solana_sdk::{signature::Keypair, signer::Signer, transaction::TransactionError};
1820
use wormhole_sdk::{Address, Vaa};
1921

2022
use crate::{
@@ -45,6 +47,7 @@ fn init_transfer_accs_args(
4547
) -> (Transfer, TransferArgs) {
4648
let accs = Transfer {
4749
payer: ctx.payer.pubkey(),
50+
sibling: test_data.ntt.sibling(OTHER_CHAIN),
4851
mint: test_data.mint,
4952
from: test_data.user_token_account,
5053
from_authority: test_data.user.pubkey(),
@@ -101,6 +104,10 @@ async fn post_transfer_vaa(
101104
test_data: &TestData,
102105
sequence: u64,
103106
amount: u64,
107+
// TODO: this is used for a negative testing of the recipient manager
108+
// address. this should not be done in the cancel flow tests, but instead a
109+
// dedicated receive transfer test suite
110+
recipient_manager: Option<&Pubkey>,
104111
recipient: &Keypair,
105112
) -> (Pubkey, ManagerMessage<NativeTokenTransfer>) {
106113
let manager_message = ManagerMessage {
@@ -116,8 +123,16 @@ async fn post_transfer_vaa(
116123
to: recipient.pubkey().to_bytes(),
117124
},
118125
};
126+
119127
let endpoint_message: EndpointMessage<WormholeEndpoint, NativeTokenTransfer> =
120-
EndpointMessage::new(OTHER_MANAGER, manager_message.clone(), vec![]);
128+
EndpointMessage::new(
129+
OTHER_MANAGER,
130+
recipient_manager
131+
.map(|k| k.to_bytes())
132+
.unwrap_or_else(|| test_data.ntt.program.to_bytes()),
133+
manager_message.clone(),
134+
vec![],
135+
);
121136

122137
let vaa = Vaa {
123138
version: 1,
@@ -160,8 +175,8 @@ async fn test_cancel() {
160175
let recipient = Keypair::new();
161176
let (mut ctx, test_data) = setup(Mode::Locking).await;
162177

163-
let (vaa0, msg0) = post_transfer_vaa(&mut ctx, &test_data, 0, 1000, &recipient).await;
164-
let (vaa1, msg1) = post_transfer_vaa(&mut ctx, &test_data, 1, 2000, &recipient).await;
178+
let (vaa0, msg0) = post_transfer_vaa(&mut ctx, &test_data, 0, 1000, None, &recipient).await;
179+
let (vaa1, msg1) = post_transfer_vaa(&mut ctx, &test_data, 1, 2000, None, &recipient).await;
165180

166181
let inbound_limit_before = inbound_capacity(&mut ctx, &test_data).await;
167182
let outbound_limit_before = outbound_capacity(&mut ctx, &test_data).await;
@@ -250,3 +265,45 @@ async fn test_cancel() {
250265
inbound_capacity(&mut ctx, &test_data).await
251266
);
252267
}
268+
269+
// TODO: this should not live in this file, move to a dedicated receive test suite
270+
#[tokio::test]
271+
async fn test_wrong_recipient_manager() {
272+
let recipient = Keypair::new();
273+
let (mut ctx, test_data) = setup(Mode::Locking).await;
274+
275+
let (vaa0, msg0) = post_transfer_vaa(
276+
&mut ctx,
277+
&test_data,
278+
0,
279+
1000,
280+
Some(&Pubkey::default()),
281+
&recipient,
282+
)
283+
.await;
284+
285+
receive_message(
286+
&test_data.ntt,
287+
init_receive_message_accs(&mut ctx, &test_data, vaa0, OTHER_CHAIN, 0),
288+
)
289+
.submit(&mut ctx)
290+
.await
291+
.unwrap();
292+
293+
let err = redeem(
294+
&test_data.ntt,
295+
init_redeem_accs(&mut ctx, &test_data, OTHER_CHAIN, msg0),
296+
RedeemArgs {},
297+
)
298+
.submit(&mut ctx)
299+
.await
300+
.unwrap_err();
301+
302+
assert_eq!(
303+
err.unwrap(),
304+
TransactionError::InstructionError(
305+
0,
306+
InstructionError::Custom(NTTError::InvalidRecipientManager.into())
307+
)
308+
);
309+
}

solana/programs/example-native-token-transfers/tests/sdk/instructions/transfer.rs

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub struct Transfer {
1313
pub mint: Pubkey,
1414
pub from: Pubkey,
1515
pub from_authority: Pubkey,
16+
pub sibling: Pubkey,
1617
pub outbox_item: Pubkey,
1718
}
1819

@@ -30,6 +31,7 @@ pub fn transfer_burn(ntt: &NTT, transfer: Transfer, args: TransferArgs) -> Instr
3031
let accounts = example_native_token_transfers::accounts::TransferBurn {
3132
common: common(ntt, &transfer),
3233
inbox_rate_limit: ntt.inbox_rate_limit(chain_id),
34+
sibling: transfer.sibling,
3335
};
3436

3537
Instruction {
@@ -46,6 +48,7 @@ pub fn transfer_lock(ntt: &NTT, transfer: Transfer, args: TransferArgs) -> Instr
4648
let accounts = example_native_token_transfers::accounts::TransferLock {
4749
common: common(ntt, &transfer),
4850
inbox_rate_limit: ntt.inbox_rate_limit(chain_id),
51+
sibling: transfer.sibling,
4952
custody: ntt.custody(&transfer.mint),
5053
};
5154
Instruction {

0 commit comments

Comments
 (0)