use anyhow::{Context, Result};
use bitcoin_hashes::hex::ToHex;
use bitcoincash::blockdata::opcodes;
use bitcoincash::blockdata::script::read_uint;
use core::slice::Iter;
pub fn read_push_from_script(mut iter: Iter<u8>) -> Result<(Iter<u8>, Option<Vec<u8>>)> {
macro_rules! take_stack_item {
($iter:expr, $len:expr) => {{
let p = $iter.by_ref().take($len).cloned().collect::<Vec<u8>>();
if p.len() < $len {
return Err(anyhow!(
"Item on stack is smaller than expected ({} > {})",
p.len(),
$len
));
}
p
}};
}
let opcode = if let Some(o) = iter.next() {
opcodes::All::from(*o)
} else {
return Ok((iter, None));
};
if let opcodes::Class::PushBytes(n) = opcode.classify(opcodes::ClassifyContext::Legacy) {
let item = Some(take_stack_item!(iter, n as usize));
return Ok((iter, item));
}
if let opcodes::Class::PushNum(n) = opcode.classify(opcodes::ClassifyContext::Legacy) {
return Ok((iter, Some((n as u8).to_be_bytes().to_vec())));
}
let n = match opcode {
opcodes::all::OP_PUSHDATA1 => {
let n = read_uint(iter.as_slice(), 1).context("Invalid PUSHDATA1")?;
iter.next().context("Truncated PUSHDATA1 length")?;
n
}
opcodes::all::OP_PUSHDATA2 => {
let n = read_uint(iter.as_slice(), 2).context("Invalid PUSHDATA2")?;
iter.next().context("Truncated PUSHDATA2 length")?;
iter.next().context("Truncated PUSHDATA2 length")?;
n
}
opcodes::all::OP_PUSHDATA4 => {
let n = read_uint(iter.as_slice(), 4).context("Invalid PUSHDATA4")?;
iter.next().context("Truncated PUSHDATA4 length")?;
iter.next().context("Truncated PUSHDATA4 length")?;
iter.next().context("Truncated PUSHDATA4 length")?;
iter.next().context("Truncated PUSHDATA4 length")?;
n
}
_ => bail!("0x{} is not a push operation", opcode.to_u8().to_hex()),
};
let item = take_stack_item!(iter, n);
Ok((iter, Some(item)))
}