Skip to content

Commit a6cdd74

Browse files
committed
Refactored config line parsing logic.
1 parent 4062619 commit a6cdd74

File tree

4 files changed

+59
-62
lines changed

4 files changed

+59
-62
lines changed

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/indexer/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ name = "holaplex-indexer-search"
6464
required-features = ["search"]
6565

6666
[dependencies]
67-
arrayref = "0.3.6"
6867
async-trait = "0.1.52"
6968
crossbeam = { version = "0.8.1", optional = true }
7069
futures-util = "0.3.21"

crates/indexer/src/geyser/accounts/candy_machine.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,13 @@ async fn process_config_lines(
9494

9595
for config_line in &config_lines {
9696
let db_config_line = CMConfigLine {
97-
candy_machine_address: Owned(bs58::encode(key).into_string()),
98-
name: Owned(config_line.0.name.trim_matches(char::from(0)).to_owned()),
99-
uri: Owned(config_line.0.uri.trim_matches(char::from(0)).to_owned()),
100-
idx: i32::try_from(config_line.1).unwrap_or(-1i32),
97+
candy_machine_address: Owned(key.to_string()),
98+
name: Owned(config_line.0.name.trim_matches('\0').to_owned()),
99+
uri: Owned(config_line.0.uri.trim_matches('\0').to_owned()),
100+
idx: i32::try_from(config_line.1).unwrap_or(-1),
101101
taken: config_line.2,
102102
};
103+
103104
db_config_lines.push(db_config_line);
104105
}
105106

@@ -124,7 +125,7 @@ async fn process_config_lines(
124125
})
125126
})
126127
.await
127-
.context("failed to insert candy machine config lines")?;
128+
.context("Failed to insert candy machine config lines")?;
128129

129130
Ok(())
130131
}

crates/indexer/src/geyser/programs/candy_machine.rs

Lines changed: 53 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use anchor_lang_v0_21::{AccountDeserialize, AnchorDeserialize};
2-
use arrayref::array_ref;
32
use mpl_candy_machine::{
43
CandyMachine, CollectionPDA, ConfigLine, CONFIG_ARRAY_START, CONFIG_LINE_SIZE,
54
};
@@ -15,13 +14,13 @@ const COLLECTION_PDA_SIZE: usize = 8 + 64;
1514
/// returns a vector containing tuples: (`config_line`, index, taken)
1615
///
1716
/// it is important that this not be called if the candy machine has hidden settings
18-
#[must_use]
1917
pub fn parse_cm_config_lines(
2018
data: &[u8],
2119
items_available: usize,
22-
) -> Vec<(ConfigLine, usize, bool)> {
23-
let config_line_start = CONFIG_ARRAY_START + 4;
24-
let available_bitmask_start = CONFIG_ARRAY_START + 4 + (items_available * CONFIG_LINE_SIZE) + 4;
20+
) -> Result<Vec<(ConfigLine, usize, bool)>> {
21+
const CONFIG_LINE_START: usize = CONFIG_ARRAY_START + 4;
22+
let available_bitmask_start = CONFIG_LINE_START + (items_available * CONFIG_LINE_SIZE) + 4;
23+
2524
// NOTE(will): you would think that (items_available / 8) incorrectly computes the length of the
2625
// "available" bitmask. i.e. if there are 7 items available, this value would be 0.
2726
// however, this is how metaplex has coded it. It ends up working because there are 4 bytes of padding
@@ -35,11 +34,15 @@ pub fn parse_cm_config_lines(
3534
// Sanity check to make sure we aren't going to overflow data
3635
// This could occur if this function is called on a candy machine that uses hiddensettings instead of
3736
// config lines
38-
let bytes_needed_for_taken_bitmask =
39-
(items_available / 8) + if items_available % 8 == 0 { 0 } else { 1 };
40-
if taken_bitmask_start + bytes_needed_for_taken_bitmask >= data.len() {
41-
// TODO(will): Log warning
42-
return Vec::new();
37+
let bytes_needed_for_taken_bitmask = items_available / 8 + (items_available % 8).min(1);
38+
let expected_taken_len = taken_bitmask_start + bytes_needed_for_taken_bitmask;
39+
40+
if expected_taken_len >= data.len() {
41+
bail!(
42+
"Config line bytes would overflow available data ({} vs {})",
43+
expected_taken_len,
44+
data.len()
45+
);
4346
}
4447

4548
// (config_line, index, taken)
@@ -52,23 +55,22 @@ pub fn parse_cm_config_lines(
5255

5356
// NOTE(will): if the config line is not available, we simply ignore it
5457
if available {
55-
let config_line_byte_offset = config_line_start + (idx * CONFIG_LINE_SIZE);
56-
let config_line_data = array_ref![data, config_line_byte_offset, CONFIG_LINE_SIZE];
57-
let config_line_result = ConfigLine::deserialize(&mut config_line_data.as_slice());
58+
let config_line_byte_offset = CONFIG_LINE_START + (idx * CONFIG_LINE_SIZE);
59+
let config_line = ConfigLine::deserialize(
60+
&mut &data[config_line_byte_offset..config_line_byte_offset + CONFIG_LINE_SIZE],
61+
)
62+
.with_context(|| format!("Failed to deserialize config line at index {}", idx))?;
63+
5864
let taken_bitmask_byte_offset = idx / 8;
5965
let taken_bitmask_bit_offset = 7 - (idx % 8);
6066
let taken_bitmask_value = data[taken_bitmask_start + taken_bitmask_byte_offset];
6167
let taken = taken_bitmask_value & (1 << taken_bitmask_bit_offset) != 0;
6268

63-
if let Ok(config_line) = config_line_result {
64-
config_lines.push((config_line, idx, taken));
65-
} else {
66-
// TODO(will): log some warning here that might alert us to a problem with this code?
67-
}
69+
config_lines.push((config_line, idx, taken));
6870
}
6971
}
7072

71-
config_lines
73+
Ok(config_lines)
7274
}
7375

7476
pub async fn process_collection_pda(client: &Client, update: AccountUpdate) -> Result<()> {
@@ -82,19 +84,19 @@ pub async fn process_cm(client: &Client, update: AccountUpdate) -> Result<()> {
8284
let candy_machine: CandyMachine = CandyMachine::try_deserialize(&mut update.data.as_slice())
8385
.context("Failed to deserialize candy_machine")?;
8486

85-
let items_available = usize::try_from(candy_machine.data.items_available);
86-
// TODO(will): log warning if conversion fails
87-
88-
match (
89-
items_available,
90-
candy_machine.data.hidden_settings.is_none(),
91-
) {
92-
(Ok(items_available), true) => {
93-
let config_lines = parse_cm_config_lines(&update.data, items_available);
94-
candy_machine::process(client, update.key, candy_machine, Some(config_lines)).await
95-
},
96-
_ => candy_machine::process(client, update.key, candy_machine, None).await,
97-
}
87+
let items_available = usize::try_from(candy_machine.data.items_available)
88+
.context("Failed to convert available item count")?;
89+
90+
let lines = if candy_machine.data.hidden_settings.is_some() {
91+
Some(
92+
parse_cm_config_lines(&update.data, items_available)
93+
.context("Failed to parse candy machine lines")?,
94+
)
95+
} else {
96+
None
97+
};
98+
99+
candy_machine::process(client, update.key, candy_machine, lines).await
98100
}
99101

100102
pub(crate) async fn process(client: &Client, update: AccountUpdate) -> Result<()> {
@@ -106,32 +108,27 @@ pub(crate) async fn process(client: &Client, update: AccountUpdate) -> Result<()
106108

107109
#[cfg(test)]
108110
mod tests {
109-
use std::{env, fs, io::Read, path::Path};
111+
use std::{env, fs, io::prelude::*, path::Path};
110112

111113
use anchor_lang_v0_21::AccountDeserialize;
112114
use mpl_candy_machine::CandyMachine;
113115

114-
use crate::geyser::programs::candy_machine::parse_cm_config_lines;
116+
use super::parse_cm_config_lines;
117+
use crate::prelude::*;
115118

116-
fn get_file_as_byte_vec<P: AsRef<Path>>(filename: P) -> Vec<u8> {
117-
let mut f = fs::File::open(&filename).expect("no file found");
118-
let mut buffer = vec![];
119-
f.read_to_end(&mut buffer).expect("File read failed");
120-
buffer
121-
}
119+
fn load_account_dump(filename: impl AsRef<Path>) -> Result<Vec<u8>> {
120+
let mut path = env::current_dir().context("Failed to get working dir")?;
121+
path.extend(["tests", "data"]);
122+
path.push(filename);
122123

123-
fn get_data_dir() -> std::path::PathBuf {
124-
let mut data_dir = env::current_dir().unwrap();
125-
data_dir.push("tests");
126-
data_dir.push("data");
127-
data_dir.to_owned()
128-
}
124+
println!("Loading: {:?}", path);
125+
126+
let mut f = fs::File::open(&path).with_context(|| format!("Failed to open {:?}", path))?;
127+
let mut buffer = vec![];
128+
f.read_to_end(&mut buffer)
129+
.with_context(|| format!("Failed to read {:?}", path))?;
129130

130-
fn load_account_dump<P: AsRef<Path>>(filename: P) -> Vec<u8> {
131-
let data_dir = get_data_dir();
132-
let full_path = data_dir.join(filename);
133-
println!("Loading: {:?}", full_path);
134-
return get_file_as_byte_vec(full_path);
131+
Ok(buffer)
135132
}
136133

137134
#[test]
@@ -146,15 +143,16 @@ mod tests {
146143

147144
for filename in filenames {
148145
println!("Reading Candy Machine {:?}", filename);
149-
let data = load_account_dump(filename);
146+
let data = load_account_dump(filename).unwrap();
150147
let cm = CandyMachine::try_deserialize(&mut data.as_slice()).unwrap();
151148
println!("Candy Machine: {}", filename);
152149

153-
let results = parse_cm_config_lines(&data, cm.data.items_available as usize);
150+
let avail = usize::try_from(cm.data.items_available).unwrap();
151+
let results = parse_cm_config_lines(&data, avail).unwrap();
154152

155153
let available_count = results.len();
156154
let mut taken_count = 0;
157-
for (_, _, taken) in results.iter() {
155+
for (_, _, taken) in &results {
158156
if *taken {
159157
taken_count += 1;
160158
}
@@ -164,7 +162,7 @@ mod tests {
164162
assert_eq!(available_count, 0);
165163
assert_eq!(taken_count, 0);
166164
} else {
167-
assert_eq!(available_count, cm.data.items_available as usize);
165+
assert_eq!(available_count, avail);
168166
assert_eq!(taken_count, cm.items_redeemed);
169167
}
170168
}

0 commit comments

Comments
 (0)