1use anyhow::anyhow;
2use sha2::{Digest, Sha256};
3use std::fs::File;
4use std::io::{BufReader, Read};
5use std::path::Path;
6
7const CONFIGURABLES_OFFSET_INSTR_LO: usize = 4;
9const CONFIGURABLES_OFFSET_INSTR_HI: usize = 5;
11const CONFIGURABLES_OFFSET_PREAMBLE: usize = CONFIGURABLES_OFFSET_INSTR_HI + 1;
13
14pub type InstructionWithBytes = (
17 Result<fuel_asm::Instruction, fuel_asm::InvalidOpcode>,
18 Vec<u8>,
19);
20
21pub struct InstructionWithBytesIterator {
23 buf_reader: BufReader<File>,
24}
25
26impl InstructionWithBytesIterator {
27 pub fn new(buf_reader: BufReader<File>) -> Self {
29 InstructionWithBytesIterator { buf_reader }
30 }
31}
32
33impl Iterator for InstructionWithBytesIterator {
34 type Item = InstructionWithBytes;
35
36 fn next(&mut self) -> Option<InstructionWithBytes> {
37 let mut buffer = [0; fuel_asm::Instruction::SIZE];
38 match self.buf_reader.read_exact(&mut buffer) {
40 Ok(_) => fuel_asm::from_bytes(buffer)
41 .next()
42 .map(|inst| (inst, buffer.to_vec())),
43 Err(_) => None,
44 }
45 }
46}
47
48pub fn parse_bytecode_to_instructions<P>(path: P) -> anyhow::Result<InstructionWithBytesIterator>
50where
51 P: AsRef<Path> + Clone,
52{
53 let f = File::open(path.clone())
54 .map_err(|_| anyhow!("{}: file not found", path.as_ref().to_string_lossy()))?;
55 let buf_reader = BufReader::new(f);
56
57 Ok(InstructionWithBytesIterator::new(buf_reader))
58}
59
60pub fn get_bytecode_id<P>(path: P) -> anyhow::Result<String>
63where
64 P: AsRef<Path> + Clone,
65{
66 let mut instructions = parse_bytecode_to_instructions(path.clone())?;
67
68 let mut first_six_instructions = Vec::with_capacity(CONFIGURABLES_OFFSET_PREAMBLE);
70 for _ in 0..CONFIGURABLES_OFFSET_PREAMBLE {
71 if let Some(instruction) = instructions.next() {
72 first_six_instructions.push(instruction);
73 } else {
74 return Err(anyhow!("Incomplete bytecode"));
75 }
76 }
77
78 let (lo_instr, low_raw) = &first_six_instructions[CONFIGURABLES_OFFSET_INSTR_LO];
79 let (hi_instr, hi_raw) = &first_six_instructions[CONFIGURABLES_OFFSET_INSTR_HI];
80
81 if let Err(fuel_asm::InvalidOpcode) = lo_instr {
82 if let Err(fuel_asm::InvalidOpcode) = hi_instr {
83 let configurables_offset = usize::from_be_bytes([
85 low_raw[0], low_raw[1], low_raw[2], low_raw[3], hi_raw[0], hi_raw[1], hi_raw[2],
86 hi_raw[3],
87 ]);
88
89 let mut hasher = Sha256::new();
91 for (_, raw) in first_six_instructions {
92 hasher.update(raw);
93 }
94
95 instructions
97 .take(
98 configurables_offset / fuel_asm::Instruction::SIZE
99 - CONFIGURABLES_OFFSET_PREAMBLE,
100 ) .for_each(|(_, raw)| {
102 hasher.update(raw);
103 });
104
105 let hash_result = hasher.finalize();
106 let bytecode_id = format!("{:x}", hash_result);
107 return Ok(bytecode_id);
108 }
109 }
110
111 Err(anyhow!("Configurables section offset not found"))
112}
113
114#[cfg(test)]
115mod test {
116 use super::*;
117
118 #[test]
119 fn test_get_bytecode_id_happy() {
120 let bytecode_id: String =
123 get_bytecode_id("tests/fixtures/bytecode/debug-counter.bin").expect("bytecode id");
124 assert_eq!(
125 bytecode_id,
126 "e65aa988cae1041b64dc2d85e496eed0e8a1d8105133bd313c17645a1859d53b".to_string()
127 );
128
129 let bytecode_id =
130 get_bytecode_id("tests/fixtures/bytecode/release-counter.bin").expect("bytecode id");
131 assert_eq!(
132 bytecode_id,
133 "42ae8352cbc892d7c7621f1d6fb42b072a08ba5968508d49f54991668d4ea141".to_string()
134 );
135
136 let bytecode_id =
137 get_bytecode_id("tests/fixtures/bytecode/debug-configurable_constants.bin")
138 .expect("bytecode id");
139 assert_eq!(
140 bytecode_id,
141 "babc3d9dcac8d48dee1e5aeb3340ff098d3c1ab8b0a28341d9291d8ff757199e".to_string()
142 );
143
144 let bytecode_id =
145 get_bytecode_id("tests/fixtures/bytecode/release-configurable_constants.bin")
146 .expect("bytecode id");
147 assert_eq!(
148 bytecode_id,
149 "2adfb515b66763fd29391bdba012921d045a0be83d89be5492bcaacc429695e9".to_string()
150 );
151 }
152
153 #[test]
154 fn test_get_bytecode_id_missing_configurable_offset() {
155 let result = get_bytecode_id(
158 "tests/fixtures/bytecode/debug-configurable_constants-missing-offset.bin",
159 );
160 assert_eq!(
161 result.unwrap_err().to_string().as_str(),
162 "Configurables section offset not found"
163 );
164 }
165
166 #[test]
167 fn test_get_bytecode_id_bad_path() {
168 let result = get_bytecode_id("tests/fixtures/bytecode/blahblahblahblah.bin");
169 assert_eq!(
170 result.unwrap_err().to_string().as_str(),
171 "tests/fixtures/bytecode/blahblahblahblah.bin: file not found"
172 );
173 }
174}