Skip to content

Commit 7058b69

Browse files
authored
solana: Add set_threshold ix (#589)
This PR adds: * `set_threshold` ix to update the threshold (`0 < threshold <= # of enabled transceivers`) * `deregister_transceiver` ix to deregister (disable transceiver in enabled transceiver bitmap): * Also decrements threshold (down to minimum 1) if # of enabled transceivers < threshold * Update `register_transceiver` ix to also act as re-enable: * If `RegisteredTransceiver` account is init for the first time, assign new id * Otherwise, reuse old id * Rust tests to verify edge cases * `createSetThresholdInstruction` and `createDeregisterTransceiverIx` TS helpers as a part of NTT namespace and `SolanaNtt` class respectively * Update IDL
1 parent 5288664 commit 7058b69

File tree

11 files changed

+586
-16
lines changed

11 files changed

+586
-16
lines changed

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,11 @@ impl Bitmap {
4848
.expect("Bitmap length must not exceed the bounds of u8")
4949
}
5050

51-
pub fn len(self) -> usize {
52-
BM::<128>::from_value(self.map).len()
51+
pub fn len(self) -> u8 {
52+
BM::<128>::from_value(self.map)
53+
.len()
54+
.try_into()
55+
.expect("Bitmap length must not exceed the bounds of u8")
5356
}
5457

5558
pub fn is_empty(self) -> bool {

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

+4
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ pub enum NTTError {
6161
IncorrectRentPayer,
6262
#[msg("InvalidMultisig")]
6363
InvalidMultisig,
64+
#[msg("ThresholdTooHigh")]
65+
ThresholdTooHigh,
66+
#[msg("InvalidTransceiverProgram")]
67+
InvalidTransceiverProgram,
6468
}
6569

6670
impl From<ScalingError> for NTTError {

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

+85-13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use ntt_messages::chain_id::ChainId;
33

44
use crate::{
55
config::Config,
6+
error::NTTError,
67
peer::NttManagerPeer,
78
queue::{inbox::InboxRateLimit, outbox::OutboxRateLimit, rate_limit::RateLimitState},
89
registered_transceiver::RegisteredTransceiver,
@@ -76,7 +77,7 @@ pub fn set_peer(ctx: Context<SetPeer>, args: SetPeerArgs) -> Result<()> {
7677
Ok(())
7778
}
7879

79-
// * Register transceivers
80+
// * Transceiver registration
8081

8182
#[derive(Accounts)]
8283
pub struct RegisterTransceiver<'info> {
@@ -91,13 +92,16 @@ pub struct RegisterTransceiver<'info> {
9192
#[account(mut)]
9293
pub payer: Signer<'info>,
9394

94-
#[account(executable)]
95+
#[account(
96+
executable,
97+
constraint = transceiver.key() != Pubkey::default() @ NTTError::InvalidTransceiverProgram
98+
)]
9599
/// CHECK: transceiver is meant to be a transceiver program. Arguably a `Program` constraint could be
96100
/// used here that wraps the Transceiver account type.
97101
pub transceiver: UncheckedAccount<'info>,
98102

99103
#[account(
100-
init,
104+
init_if_needed,
101105
space = 8 + RegisteredTransceiver::INIT_SPACE,
102106
payer = payer,
103107
seeds = [RegisteredTransceiver::SEED_PREFIX, transceiver.key().as_ref()],
@@ -109,17 +113,62 @@ pub struct RegisterTransceiver<'info> {
109113
}
110114

111115
pub fn register_transceiver(ctx: Context<RegisterTransceiver>) -> Result<()> {
112-
let id = ctx.accounts.config.next_transceiver_id;
113-
ctx.accounts.config.next_transceiver_id += 1;
116+
// initialize registered transceiver with new id on init
117+
if ctx.accounts.registered_transceiver.transceiver_address == Pubkey::default() {
118+
let id = ctx.accounts.config.next_transceiver_id;
119+
ctx.accounts.config.next_transceiver_id += 1;
120+
ctx.accounts
121+
.registered_transceiver
122+
.set_inner(RegisteredTransceiver {
123+
bump: ctx.bumps.registered_transceiver,
124+
id,
125+
transceiver_address: ctx.accounts.transceiver.key(),
126+
});
127+
}
128+
114129
ctx.accounts
115-
.registered_transceiver
116-
.set_inner(RegisteredTransceiver {
117-
bump: ctx.bumps.registered_transceiver,
118-
id,
119-
transceiver_address: ctx.accounts.transceiver.key(),
120-
});
121-
122-
ctx.accounts.config.enabled_transceivers.set(id, true)?;
130+
.config
131+
.enabled_transceivers
132+
.set(ctx.accounts.registered_transceiver.id, true)?;
133+
Ok(())
134+
}
135+
136+
#[derive(Accounts)]
137+
pub struct DeregisterTransceiver<'info> {
138+
#[account(
139+
mut,
140+
has_one = owner,
141+
)]
142+
pub config: Account<'info, Config>,
143+
144+
#[account(mut)]
145+
pub owner: Signer<'info>,
146+
147+
#[account(executable)]
148+
/// CHECK: transceiver is meant to be a transceiver program. Arguably a `Program` constraint could be
149+
/// used here that wraps the Transceiver account type.
150+
pub transceiver: UncheckedAccount<'info>,
151+
152+
#[account(
153+
seeds = [RegisteredTransceiver::SEED_PREFIX, transceiver.key().as_ref()],
154+
bump,
155+
constraint = config.enabled_transceivers.get(registered_transceiver.id)? @ NTTError::DisabledTransceiver,
156+
)]
157+
pub registered_transceiver: Account<'info, RegisteredTransceiver>,
158+
}
159+
160+
pub fn deregister_transceiver(ctx: Context<DeregisterTransceiver>) -> Result<()> {
161+
ctx.accounts
162+
.config
163+
.enabled_transceivers
164+
.set(ctx.accounts.registered_transceiver.id, false)?;
165+
166+
// decrement threshold if too high
167+
let num_enabled_transceivers = ctx.accounts.config.enabled_transceivers.len();
168+
if num_enabled_transceivers < ctx.accounts.config.threshold {
169+
// threshold should be at least 1
170+
ctx.accounts.config.threshold = num_enabled_transceivers.max(1);
171+
}
123172
Ok(())
124173
}
125174

@@ -200,3 +249,26 @@ pub fn set_paused(ctx: Context<SetPaused>, paused: bool) -> Result<()> {
200249
ctx.accounts.config.paused = paused;
201250
Ok(())
202251
}
252+
253+
// * Set Threshold
254+
255+
#[derive(Accounts)]
256+
#[instruction(threshold: u8)]
257+
pub struct SetThreshold<'info> {
258+
pub owner: Signer<'info>,
259+
260+
#[account(
261+
mut,
262+
has_one = owner,
263+
constraint = threshold <= config.enabled_transceivers.len() @ NTTError::ThresholdTooHigh
264+
)]
265+
pub config: Account<'info, Config>,
266+
}
267+
268+
pub fn set_threshold(ctx: Context<SetThreshold>, threshold: u8) -> Result<()> {
269+
if threshold == 0 {
270+
return Err(NTTError::ZeroThreshold.into());
271+
}
272+
ctx.accounts.config.threshold = threshold;
273+
Ok(())
274+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ fn initialize_config_and_rate_limit(
155155
pending_owner: None,
156156
paused: false,
157157
next_transceiver_id: 0,
158-
// NOTE: can't be changed for now
158+
// NOTE: can be changed via `set_threshold` ix
159159
threshold: 1,
160160
enabled_transceivers: Bitmap::new(),
161161
custody: common.custody.key(),

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

+8
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,10 @@ pub mod example_native_token_transfers {
188188
instructions::register_transceiver(ctx)
189189
}
190190

191+
pub fn deregister_transceiver(ctx: Context<DeregisterTransceiver>) -> Result<()> {
192+
instructions::deregister_transceiver(ctx)
193+
}
194+
191195
pub fn set_outbound_limit(
192196
ctx: Context<SetOutboundLimit>,
193197
args: SetOutboundLimitArgs,
@@ -206,6 +210,10 @@ pub mod example_native_token_transfers {
206210
instructions::mark_outbox_item_as_released(ctx)
207211
}
208212

213+
pub fn set_threshold(ctx: Context<SetThreshold>, threshold: u8) -> Result<()> {
214+
instructions::set_threshold(ctx, threshold)
215+
}
216+
209217
// standalone transceiver stuff
210218

211219
pub fn set_wormhole_peer(

0 commit comments

Comments
 (0)