Skip to content

Commit 76211b2

Browse files
committed
solana: Add error handling for Bitmap indexing
1 parent 235ae3c commit 76211b2

File tree

9 files changed

+67
-28
lines changed

9 files changed

+67
-28
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use crate::error::NTTError;
12
use anchor_lang::prelude::*;
23
use bitmaps::Bitmap as BM;
3-
4+
use std::result::Result as StdResult;
45
#[derive(PartialEq, Eq, Clone, Copy, Debug, AnchorDeserialize, AnchorSerialize, InitSpace)]
56
pub struct Bitmap {
67
map: u128,
@@ -13,6 +14,8 @@ impl Default for Bitmap {
1314
}
1415

1516
impl Bitmap {
17+
pub const BITS: u8 = 128;
18+
1619
pub fn new() -> Self {
1720
Bitmap { map: 0 }
1821
}
@@ -21,19 +24,30 @@ impl Bitmap {
2124
Bitmap { map: value }
2225
}
2326

24-
pub fn set(&mut self, index: u8, value: bool) {
27+
pub fn set(&mut self, index: u8, value: bool) -> StdResult<(), NTTError> {
28+
if index >= Self::BITS {
29+
return Err(NTTError::BitmapIndexOutOfBounds);
30+
}
2531
let mut bm = BM::<128>::from_value(self.map);
26-
bm.set(index as usize, value);
32+
bm.set(usize::from(index), value);
2733
self.map = *bm.as_value();
34+
Ok(())
2835
}
2936

30-
pub fn get(&self, index: u8) -> bool {
31-
BM::<128>::from_value(self.map).get(index as usize)
37+
pub fn get(&self, index: u8) -> StdResult<bool, NTTError> {
38+
if index >= Self::BITS {
39+
return Err(NTTError::BitmapIndexOutOfBounds);
40+
}
41+
Ok(BM::<128>::from_value(self.map).get(usize::from(index)))
3242
}
3343

3444
pub fn count_enabled_votes(&self, enabled: Bitmap) -> u8 {
3545
let bm = BM::<128>::from_value(self.map) & BM::<128>::from_value(enabled.map);
36-
bm.len() as u8
46+
// Conversion from usize to u8 is safe here. The Bitmap uses u128, so its maximum length
47+
// (number of true bits) is 128.
48+
bm.len()
49+
.try_into()
50+
.expect("Bitmap length must not exceed the bounds of u8")
3751
}
3852
}
3953

@@ -46,22 +60,40 @@ mod tests {
4660
let mut enabled = Bitmap::from_value(u128::MAX);
4761
let mut bm = Bitmap::new();
4862
assert_eq!(bm.count_enabled_votes(enabled), 0);
49-
bm.set(0, true);
63+
bm.set(0, true).unwrap();
5064
assert_eq!(bm.count_enabled_votes(enabled), 1);
51-
assert!(bm.get(0));
52-
assert!(!bm.get(1));
53-
bm.set(1, true);
65+
assert!(bm.get(0).unwrap());
66+
assert!(!bm.get(1).unwrap());
67+
bm.set(1, true).unwrap();
5468
assert_eq!(bm.count_enabled_votes(enabled), 2);
55-
assert!(bm.get(0));
56-
assert!(bm.get(1));
57-
bm.set(0, false);
69+
assert!(bm.get(0).unwrap());
70+
assert!(bm.get(1).unwrap());
71+
bm.set(0, false).unwrap();
5872
assert_eq!(bm.count_enabled_votes(enabled), 1);
59-
assert!(!bm.get(0));
60-
assert!(bm.get(1));
61-
bm.set(18, true);
73+
assert!(!bm.get(0).unwrap());
74+
assert!(bm.get(1).unwrap());
75+
bm.set(18, true).unwrap();
6276
assert_eq!(bm.count_enabled_votes(enabled), 2);
6377

64-
enabled.set(18, false);
78+
enabled.set(18, false).unwrap();
6579
assert_eq!(bm.count_enabled_votes(enabled), 1);
6680
}
81+
82+
#[test]
83+
fn test_bitmap_len() {
84+
let max_bitmap = Bitmap::from_value(u128::MAX);
85+
assert_eq!(128, max_bitmap.count_enabled_votes(max_bitmap));
86+
}
87+
88+
#[test]
89+
fn test_bitmap_get_out_of_bounds() {
90+
let bm = Bitmap::new();
91+
assert_eq!(bm.get(129), Err(NTTError::BitmapIndexOutOfBounds));
92+
}
93+
94+
#[test]
95+
fn test_bitmap_set_out_of_bounds() {
96+
let mut bm = Bitmap::new();
97+
assert_eq!(bm.set(129, false), Err(NTTError::BitmapIndexOutOfBounds));
98+
}
6799
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ pub struct Config {
3131
/// The number of transceivers that must attest to a transfer before it is
3232
/// accepted.
3333
pub threshold: u8,
34-
/// Bitmap of enabled transceivers
34+
/// Bitmap of enabled transceivers.
35+
/// The maximum number of transceivers is equal to [`Bitmap::BITS`].
3536
pub enabled_transceivers: Bitmap,
3637
/// Pause the program. This is useful for upgrades and other maintenance.
3738
pub paused: bool,

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

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use ntt_messages::errors::ScalingError;
33

44
#[error_code]
55
// TODO(csongor): rename
6+
#[derive(PartialEq)]
67
pub enum NTTError {
78
#[msg("CantReleaseYet")]
89
CantReleaseYet,
@@ -48,6 +49,8 @@ pub enum NTTError {
4849
OverflowExponent,
4950
#[msg("OverflowScaledAmount")]
5051
OverflowScaledAmount,
52+
#[msg("BitmapIndexOutOfBounds")]
53+
BitmapIndexOutOfBounds,
5154
}
5255

5356
impl From<ScalingError> for NTTError {

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ pub fn register_transceiver(ctx: Context<RegisterTransceiver>) -> Result<()> {
229229
transceiver_address: ctx.accounts.transceiver.key(),
230230
});
231231

232-
ctx.accounts.config.enabled_transceivers.set(id, true);
232+
ctx.accounts.config.enabled_transceivers.set(id, true)?;
233233
Ok(())
234234
}
235235

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub struct Redeem<'info> {
4646
pub transceiver_message: Account<'info, ValidatedTransceiverMessage<NativeTokenTransfer>>,
4747

4848
#[account(
49-
constraint = config.enabled_transceivers.get(transceiver.id) @ NTTError::DisabledTransceiver
49+
constraint = config.enabled_transceivers.get(transceiver.id)? @ NTTError::DisabledTransceiver
5050
)]
5151
pub transceiver: Account<'info, RegisteredTransceiver>,
5252

@@ -131,7 +131,7 @@ pub fn redeem(ctx: Context<Redeem>, _args: RedeemArgs) -> Result<()> {
131131
}
132132

133133
// idempotent
134-
accs.inbox_item.votes.set(accs.transceiver.id, true);
134+
accs.inbox_item.votes.set(accs.transceiver.id, true)?;
135135

136136
if accs
137137
.inbox_item

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ impl OutboxItem {
3131
return Ok(false);
3232
}
3333

34-
if self.released.get(transceiver_index) {
34+
if self.released.get(transceiver_index)? {
3535
return Err(NTTError::MessageAlreadySent.into());
3636
}
3737

38-
self.released.set(transceiver_index, true);
38+
self.released.set(transceiver_index, true)?;
3939

4040
Ok(true)
4141
}

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ pub struct ReleaseOutbound<'info> {
1919

2020
#[account(
2121
mut,
22-
constraint = !outbox_item.released.get(transceiver.id) @ NTTError::MessageAlreadySent,
22+
constraint = !outbox_item.released.get(transceiver.id)? @ NTTError::MessageAlreadySent,
2323
)]
2424
pub outbox_item: Account<'info, OutboxItem>,
2525

2626
#[account(
2727
constraint = transceiver.transceiver_address == crate::ID,
28-
constraint = config.enabled_transceivers.get(transceiver.id) @ NTTError::DisabledTransceiver
28+
constraint = config.enabled_transceivers.get(transceiver.id)? @ NTTError::DisabledTransceiver
2929
)]
3030
pub transceiver: Account<'info, RegisteredTransceiver>,
3131

@@ -65,7 +65,7 @@ pub fn release_outbound(ctx: Context<ReleaseOutbound>, args: ReleaseOutboundArgs
6565
}
6666
}
6767

68-
assert!(accs.outbox_item.released.get(accs.transceiver.id));
68+
assert!(accs.outbox_item.released.get(accs.transceiver.id)?);
6969
let message: TransceiverMessage<WormholeTransceiver, NativeTokenTransfer> =
7070
TransceiverMessage::new(
7171
// TODO: should we just put the ntt id here statically?

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ async fn assert_queued(ctx: &mut ProgramTestContext, outbox_item: Pubkey) {
370370

371371
let clock: Clock = ctx.banks_client.get_sysvar().await.unwrap();
372372

373-
assert!(!outbox_item_account.released.get(0));
373+
assert!(!outbox_item_account.released.get(0).unwrap());
374374
assert!(outbox_item_account.release_timestamp > clock.unix_timestamp);
375375
}
376376

solana/programs/ntt-quoter/src/processor/request_relay.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ fn check_release_constraint_and_fetch_chain_id(
1919
let mut acc_data: &[_] = &outbox_item.try_borrow_data()?;
2020
let item = OutboxItem::try_deserialize(&mut acc_data)?;
2121

22-
if !item.released.get(registered_ntt.wormhole_transceiver_index) {
22+
let released = item
23+
.released
24+
.get(registered_ntt.wormhole_transceiver_index)?;
25+
if !released {
2326
return Err(NttQuoterError::OutboxItemNotReleased.into());
2427
}
2528

0 commit comments

Comments
 (0)