Skip to content

Commit fb8a087

Browse files
authored
solana: Update deregister_transceiver to error when trying to remove last enabled transceiver (#698)
Resolves: #692 This returns `ZeroThreshold` error to mimic EVM that implicitly does the same. This PR also adds a unit test to check for this invariant.
1 parent 395383c commit fb8a087

File tree

2 files changed

+80
-17
lines changed
  • solana/programs/example-native-token-transfers

2 files changed

+80
-17
lines changed

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ pub fn register_transceiver(ctx: Context<RegisterTransceiver>) -> Result<()> {
142142
Ok(())
143143
}
144144

145+
// TODO: Rename to DisableTransceiver
145146
#[derive(Accounts)]
146147
pub struct DeregisterTransceiver<'info> {
147148
#[account(
@@ -166,11 +167,14 @@ pub fn deregister_transceiver(ctx: Context<DeregisterTransceiver>) -> Result<()>
166167
.enabled_transceivers
167168
.set(ctx.accounts.registered_transceiver.id, false)?;
168169

169-
// decrement threshold if too high
170170
let num_enabled_transceivers = ctx.accounts.config.enabled_transceivers.len();
171+
// at least one transceiver should be enabled
172+
if num_enabled_transceivers == 0 {
173+
return Err(NTTError::ZeroThreshold.into());
174+
}
175+
// decrement threshold if too high
171176
if num_enabled_transceivers < ctx.accounts.config.threshold {
172-
// threshold should be at least 1
173-
ctx.accounts.config.threshold = num_enabled_transceivers.max(1);
177+
ctx.accounts.config.threshold = num_enabled_transceivers;
174178
}
175179
Ok(())
176180
}

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

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -118,23 +118,11 @@ async fn test_reregister_all_transceivers() {
118118
.submit_with_signers(&[&test_data.program_owner], &mut ctx)
119119
.await
120120
.unwrap();
121+
// assert threshold decreases
121122
assert_threshold(&mut ctx, num_dummy_transceivers - idx as u8).await;
122123
}
123124

124-
// deregister baked-in transceiver
125-
deregister_transceiver(
126-
&good_ntt,
127-
DeregisterTransceiver {
128-
owner: test_data.program_owner.pubkey(),
129-
transceiver: example_native_token_transfers::ID,
130-
},
131-
)
132-
.submit_with_signers(&[&test_data.program_owner], &mut ctx)
133-
.await
134-
.unwrap();
135-
assert_threshold(&mut ctx, 1).await;
136-
137-
// reregister dummy transceiver
125+
// reregister dummy transceivers
138126
for (idx, transceiver) in dummy_transceivers.iter().enumerate() {
139127
register_transceiver(
140128
&good_ntt,
@@ -147,6 +135,7 @@ async fn test_reregister_all_transceivers() {
147135
.submit_with_signers(&[&test_data.program_owner], &mut ctx)
148136
.await
149137
.unwrap();
138+
// assert transceiver_id and threshold are retained
150139
assert_transceiver_id(&mut ctx, transceiver, idx as u8 + 1).await;
151140
assert_threshold(&mut ctx, 1).await;
152141
}
@@ -163,10 +152,80 @@ async fn test_reregister_all_transceivers() {
163152
.submit_with_signers(&[&test_data.program_owner], &mut ctx)
164153
.await
165154
.unwrap();
155+
// assert transceiver_id and threshold are retained
166156
assert_transceiver_id(&mut ctx, &example_native_token_transfers::ID, 0).await;
167157
assert_threshold(&mut ctx, 1).await;
168158
}
169159

160+
#[tokio::test]
161+
async fn test_deregister_last_enabled_transceiver() {
162+
let (mut ctx, test_data) = setup(Mode::Locking).await;
163+
164+
// attempt to deregister only enabled transceiver (baked-in transceiver)
165+
let err = deregister_transceiver(
166+
&good_ntt,
167+
DeregisterTransceiver {
168+
owner: test_data.program_owner.pubkey(),
169+
transceiver: example_native_token_transfers::ID,
170+
},
171+
)
172+
.submit_with_signers(&[&test_data.program_owner], &mut ctx)
173+
.await
174+
.unwrap_err();
175+
assert_eq!(
176+
err.unwrap(),
177+
TransactionError::InstructionError(
178+
0,
179+
InstructionError::Custom(NTTError::ZeroThreshold.into())
180+
)
181+
);
182+
183+
// register arbitrary executable program as dummy transceiver
184+
let dummy_transceiver = wormhole_anchor_sdk::wormhole::program::Wormhole::id();
185+
register_transceiver(
186+
&good_ntt,
187+
RegisterTransceiver {
188+
payer: ctx.payer.pubkey(),
189+
owner: test_data.program_owner.pubkey(),
190+
transceiver: dummy_transceiver,
191+
},
192+
)
193+
.submit_with_signers(&[&test_data.program_owner], &mut ctx)
194+
.await
195+
.unwrap();
196+
197+
// deregister baked-in transceiver
198+
deregister_transceiver(
199+
&good_ntt,
200+
DeregisterTransceiver {
201+
owner: test_data.program_owner.pubkey(),
202+
transceiver: example_native_token_transfers::ID,
203+
},
204+
)
205+
.submit_with_signers(&[&test_data.program_owner], &mut ctx)
206+
.await
207+
.unwrap();
208+
209+
// attempt to deregister last enabled transceiver (dummy transceiver)
210+
let err = deregister_transceiver(
211+
&good_ntt,
212+
DeregisterTransceiver {
213+
owner: test_data.program_owner.pubkey(),
214+
transceiver: dummy_transceiver,
215+
},
216+
)
217+
.submit_with_signers(&[&test_data.program_owner], &mut ctx)
218+
.await
219+
.unwrap_err();
220+
assert_eq!(
221+
err.unwrap(),
222+
TransactionError::InstructionError(
223+
0,
224+
InstructionError::Custom(NTTError::ZeroThreshold.into())
225+
)
226+
);
227+
}
228+
170229
#[tokio::test]
171230
async fn test_zero_threshold() {
172231
let (mut ctx, test_data) = setup(Mode::Locking).await;

0 commit comments

Comments
 (0)