#[macro_use]
mod macros;
use crate::MAX_SCRIPT_ELEMENT_SIZE;
use crate::zk_precompiles::{parse_tag, verify_zk};
use crate::{
LOCK_TIME_THRESHOLD, MAX_TX_IN_SEQUENCE_NUM, NO_COST_OPCODE, SEQUENCE_LOCK_TIME_DISABLED, SEQUENCE_LOCK_TIME_MASK, ScriptSource,
SpkEncoding, TxScriptEngine, TxScriptError,
data_stack::{OpcodeData, StackEntry, serialize_i64},
};
use blake2b_simd::Params;
use kaspa_consensus_core::hashing::sighash::SigHashReusedValues;
use kaspa_consensus_core::hashing::sighash_type::SigHashType;
use kaspa_consensus_core::mass::ScriptUnits;
use kaspa_consensus_core::tx::VerifiableTransaction;
use kaspa_hashes::Hash;
use kaspa_hashes::ZERO_HASH;
use kaspa_utils::hex::FromHex;
use sha2::{Digest, Sha256};
use std::{
fmt::{Debug, Formatter},
num::TryFromIntError,
};
pub const OP_SMALL_INT_MIN_VAL: u8 = 1;
pub const OP_SMALL_INT_MAX_VAL: u8 = 16;
pub const OP_DATA_MIN_VAL: u8 = self::codes::OpData1;
pub const OP_DATA_MAX_VAL: u8 = self::codes::OpData75;
pub const OP_1_NEGATE_VAL: u8 = 0x81;
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum OpCond {
False,
True,
Skip,
}
impl OpCond {
pub fn negate(&self) -> OpCond {
match self {
OpCond::True => OpCond::False,
OpCond::False => OpCond::True,
OpCond::Skip => OpCond::Skip,
}
}
}
type OpCodeResult = Result<(), TxScriptError>;
#[derive(Copy, Clone)]
enum HashOpcodePricing {
Blake2b,
Blake3,
Sha256,
}
impl HashOpcodePricing {
const fn units_per_byte(self) -> u64 {
match self {
Self::Blake2b => 2,
Self::Blake3 => 1,
Self::Sha256 => 1,
}
}
fn script_units_for_data(self, data_len: usize) -> ScriptUnits {
ScriptUnits((data_len as u64).saturating_mul(self.units_per_byte()))
}
}
pub(crate) struct OpCode<const CODE: u8> {
data: Vec<u8>,
}
impl<const CODE: u8> Debug for OpCode<CODE> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Opcode<{:#2x}>{{ data:{:?} }}", CODE, self.data)
}
}
pub trait OpCodeMetadata: Debug {
fn value(&self) -> u8;
fn len(&self) -> usize;
fn is_conditional(&self) -> bool;
fn is_disabled(&self) -> bool;
fn always_illegal(&self) -> bool;
fn is_push_opcode(&self) -> bool;
fn get_data(&self) -> &[u8];
fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub trait OpCodeExecution<T: VerifiableTransaction, Reused: SigHashReusedValues> {
fn empty() -> Result<Box<dyn OpCodeImplementation<T, Reused>>, TxScriptError>
where
Self: Sized;
#[allow(clippy::new_ret_no_self)]
fn new(data: Vec<u8>) -> Result<Box<dyn OpCodeImplementation<T, Reused>>, TxScriptError>
where
Self: Sized;
fn execute(&self, vm: &mut TxScriptEngine<T, Reused>) -> OpCodeResult;
}
pub trait OpcodeSerialization {
fn serialize(&self) -> Vec<u8>;
fn deserialize<'i, I: Iterator<Item = &'i u8>, T: VerifiableTransaction, Reused: SigHashReusedValues>(
it: &mut I,
) -> Result<Box<dyn OpCodeImplementation<T, Reused>>, TxScriptError>
where
Self: Sized;
}
pub trait OpCodeImplementation<T: VerifiableTransaction, Reused: SigHashReusedValues>:
OpCodeExecution<T, Reused> + OpCodeMetadata + OpcodeSerialization + std::fmt::Display
{
}
impl<const CODE: u8> OpCodeMetadata for OpCode<CODE> {
fn value(&self) -> u8 {
CODE
}
fn is_disabled(&self) -> bool {
matches!(CODE, codes::OpLeft | codes::OpRight | codes::Op2Mul | codes::Op2Div | codes::OpLShift | codes::OpRShift)
}
fn always_illegal(&self) -> bool {
matches!(CODE, codes::OpVerIf | codes::OpVerNotIf)
}
fn is_push_opcode(&self) -> bool {
CODE <= NO_COST_OPCODE
}
fn len(&self) -> usize {
self.data.len()
}
fn is_conditional(&self) -> bool {
self.value() >= 0x63 && self.value() <= 0x68
}
fn get_data(&self) -> &[u8] {
&self.data
}
}
#[inline]
fn push_literal_data<T: VerifiableTransaction, Reused: SigHashReusedValues>(
data: Vec<u8>,
vm: &mut TxScriptEngine<T, Reused>,
) -> OpCodeResult {
vm.dstack.push_unmetered(data.into())
}
#[inline]
fn push_data<T: VerifiableTransaction, Reused: SigHashReusedValues>(
data: Vec<u8>,
vm: &mut TxScriptEngine<T, Reused>,
) -> OpCodeResult {
vm.dstack.push(data.into())
}
#[inline]
fn push_literal_number<T: VerifiableTransaction, Reused: SigHashReusedValues>(
number: i64,
vm: &mut TxScriptEngine<T, Reused>,
) -> OpCodeResult {
vm.dstack.push_item_unmetered(number)
}
#[inline]
fn push_number<T: VerifiableTransaction, Reused: SigHashReusedValues>(
number: i64,
vm: &mut TxScriptEngine<T, Reused>,
) -> OpCodeResult {
vm.dstack.push_item(number)?;
Ok(())
}
fn substring(data: &[u8], start: usize, end: usize) -> Result<Vec<u8>, TxScriptError> {
let diff = end.checked_sub(start).ok_or(TxScriptError::InvalidRange { start, end })?;
if diff > MAX_SCRIPT_ELEMENT_SIZE {
return Err(TxScriptError::ElementTooBig(diff, MAX_SCRIPT_ELEMENT_SIZE));
}
data.get(start..end).map(<[u8]>::to_vec).ok_or(TxScriptError::OutOfBoundsSubstring(start, end, data.len()))
}
pub(crate) fn i32_to_usize(value: i32) -> Result<usize, TxScriptError> {
value.try_into().map_err(|_| TxScriptError::InvalidIndex(value))
}
pub(crate) fn i32s_to_usizes<const N: usize>(arr: [i32; N]) -> Result<[usize; N], TxScriptError> {
let mut out = [0usize; N];
for i in 0..N {
out[i] = i32_to_usize(arr[i])?;
}
Ok(out)
}
opcode_list! {
opcode |Op0| OpFalse<0x00, 1>(self , vm) {
push_literal_data(vec![], vm)
}
opcode OpData1<0x01, 2>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData2<0x02, 3>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData3<0x03, 4>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData4<0x04, 5>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData5<0x05, 6>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData6<0x06, 7>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData7<0x07, 8>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData8<0x08, 9>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData9<0x09, 10>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData10<0x0a, 11>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData11<0x0b, 12>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData12<0x0c, 13>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData13<0x0d, 14>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData14<0x0e, 15>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData15<0x0f, 16>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData16<0x10, 17>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData17<0x11, 18>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData18<0x12, 19>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData19<0x13, 20>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData20<0x14, 21>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData21<0x15, 22>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData22<0x16, 23>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData23<0x17, 24>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData24<0x18, 25>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData25<0x19, 26>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData26<0x1a, 27>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData27<0x1b, 28>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData28<0x1c, 29>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData29<0x1d, 30>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData30<0x1e, 31>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData31<0x1f, 32>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData32<0x20, 33>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData33<0x21, 34>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData34<0x22, 35>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData35<0x23, 36>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData36<0x24, 37>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData37<0x25, 38>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData38<0x26, 39>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData39<0x27, 40>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData40<0x28, 41>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData41<0x29, 42>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData42<0x2a, 43>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData43<0x2b, 44>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData44<0x2c, 45>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData45<0x2d, 46>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData46<0x2e, 47>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData47<0x2f, 48>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData48<0x30, 49>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData49<0x31, 50>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData50<0x32, 51>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData51<0x33, 52>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData52<0x34, 53>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData53<0x35, 54>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData54<0x36, 55>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData55<0x37, 56>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData56<0x38, 57>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData57<0x39, 58>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData58<0x3a, 59>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData59<0x3b, 60>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData60<0x3c, 61>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData61<0x3d, 62>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData62<0x3e, 63>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData63<0x3f, 64>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData64<0x40, 65>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData65<0x41, 66>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData66<0x42, 67>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData67<0x43, 68>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData68<0x44, 69>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData69<0x45, 70>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData70<0x46, 71>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData71<0x47, 72>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData72<0x48, 73>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData73<0x49, 74>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData74<0x4a, 75>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpData75<0x4b, 76>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpPushData1<0x4c, u8>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpPushData2<0x4d, u16>(self, vm) push_literal_data(self.data.clone(), vm)
opcode OpPushData4<0x4e, u32>(self, vm) push_literal_data(self.data.clone(), vm)
opcode Op1Negate<0x4f, 1>(self, vm) push_literal_number(-1, vm)
opcode OpReserved<0x50, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}")))
opcode |Op1| OpTrue<0x51, 1>(self, vm) push_literal_number(1, vm)
opcode Op2<0x52, 1>(self, vm) push_literal_number(2, vm)
opcode Op3<0x53, 1>(self, vm) push_literal_number(3, vm)
opcode Op4<0x54, 1>(self, vm) push_literal_number(4, vm)
opcode Op5<0x55, 1>(self, vm) push_literal_number(5, vm)
opcode Op6<0x56, 1>(self, vm) push_literal_number(6, vm)
opcode Op7<0x57, 1>(self, vm) push_literal_number(7, vm)
opcode Op8<0x58, 1>(self, vm) push_literal_number(8, vm)
opcode Op9<0x59, 1>(self, vm) push_literal_number(9, vm)
opcode Op10<0x5a, 1>(self, vm) push_literal_number(10, vm)
opcode Op11<0x5b, 1>(self, vm) push_literal_number(11, vm)
opcode Op12<0x5c, 1>(self, vm) push_literal_number(12, vm)
opcode Op13<0x5d, 1>(self, vm) push_literal_number(13, vm)
opcode Op14<0x5e, 1>(self, vm) push_literal_number(14, vm)
opcode Op15<0x5f, 1>(self, vm) push_literal_number(15, vm)
opcode Op16<0x60, 1>(self, vm) push_literal_number(16, vm)
opcode OpNop<0x61, 1>(self, vm) Ok(())
opcode OpVer<0x62, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}")))
opcode OpIf<0x63, 1>(self, vm) {
let cond = if vm.is_executing() {
let [cond]: [bool; 1] = vm.dstack.pop_items()?;
if cond {
OpCond::True
} else {
OpCond::False
}
} else {
OpCond::Skip
};
vm.cond_stack.push(cond);
Ok(())
}
opcode OpNotIf<0x64, 1>(self, vm) {
let cond = if vm.is_executing() {
let [cond]: [bool; 1] = vm.dstack.pop_items()?;
if cond {
OpCond::False
} else {
OpCond::True
}
}else{
OpCond::Skip
};
vm.cond_stack.push(cond);
Ok(())
}
opcode OpVerIf<0x65, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}")))
opcode OpVerNotIf<0x66, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}")))
opcode OpElse<0x67, 1>(self, vm) {
if let Some(cond) = vm.cond_stack.last_mut() {
*cond = cond.negate();
Ok(())
} else {
Err(TxScriptError::InvalidState("condition stack empty".to_string()))
}
}
opcode OpEndIf<0x68, 1>(self, vm) {
match vm.cond_stack.pop() {
None => Err(TxScriptError::InvalidState("condition stack empty".to_string())),
_ => Ok(())
}
}
opcode OpVerify<0x69, 1>(self, vm) {
let [result]: [bool; 1] = vm.dstack.pop_items()?;
match result {
true => Ok(()),
false => Err(TxScriptError::VerifyError)
}
}
opcode OpReturn<0x6a, 1>(self, vm) Err(TxScriptError::EarlyReturn)
opcode OpToAltStack<0x6b, 1>(self, vm) {
let [item] = vm.dstack.pop_raw()?;
vm.astack.push_unmetered(item)
}
opcode OpFromAltStack<0x6c, 1>(self, vm) {
let last = vm.astack.pop()?;
vm.dstack.push_unmetered(last)
}
opcode Op2Drop<0x6d, 1>(self, vm) vm.dstack.drop_items::<2>()
opcode Op2Dup<0x6e, 1>(self, vm) vm.dstack.dup_items::<2>()
opcode Op3Dup<0x6f, 1>(self, vm) vm.dstack.dup_items::<3>()
opcode Op2Over<0x70, 1>(self, vm) vm.dstack.over_items::<2>()
opcode Op2Rot<0x71, 1>(self, vm) vm.dstack.rot_items::<2>()
opcode Op2Swap<0x72, 1>(self, vm) vm.dstack.swap_items::<2>()
opcode OpIfDup<0x73, 1>(self, vm) {
let [result] = vm.dstack.peek_raw()?;
if <StackEntry as OpcodeData<bool>>::deserialize(&result)? {
vm.dstack.push(result)?;
}
Ok(())
}
opcode OpDepth<0x74, 1>(self, vm) push_number(vm.dstack.len() as i64, vm)
opcode OpDrop<0x75, 1>(self, vm) vm.dstack.drop_items::<1>()
opcode OpDup<0x76, 1>(self, vm) vm.dstack.dup_items::<1>()
opcode OpNip<0x77, 1>(self, vm) {
match vm.dstack.len() >= 2 {
true => {
vm.dstack.remove(vm.dstack.len()-2);
Ok(())
}
false => Err(TxScriptError::InvalidStackOperation(2, vm.dstack.len())),
}
}
opcode OpOver<0x78, 1>(self, vm) vm.dstack.over_items::<1>()
opcode OpPick<0x79, 1>(self, vm) {
let [loc]: [i32; 1] = vm.dstack.pop_items()?;
if loc < 0 || loc as usize >= vm.dstack.len() {
return Err(TxScriptError::InvalidState("pick at an invalid location".to_string()));
}
vm.dstack.push(vm.dstack[vm.dstack.len()-(loc as usize)-1].clone())
}
opcode OpRoll<0x7a, 1>(self, vm) {
let [loc]: [i32; 1] = vm.dstack.pop_items()?;
if loc < 0 || loc as usize >= vm.dstack.len() {
return Err(TxScriptError::InvalidState("roll at an invalid location".to_string()));
}
vm.dstack.roll(loc as usize)
}
opcode OpRot<0x7b, 1>(self, vm) vm.dstack.rot_items::<1>()
opcode OpSwap<0x7c, 1>(self, vm) vm.dstack.swap_items::<1>()
opcode OpTuck<0x7d, 1>(self, vm) {
match vm.dstack.len() >= 2 {
true => {
vm.dstack.insert(vm.dstack.len()-2, vm.dstack.last().expect("We have at least two items").clone())?;
Ok(())
}
false => Err(TxScriptError::InvalidStackOperation(2, vm.dstack.len()))
}
}
opcode OpCat<0x7e, 1>(self, vm) {
let b = vm.dstack.pop()?;
let a = vm.dstack.pop()?;
let mut r = a;
r.extend_from_slice(&b);
vm.dstack.push(r)
}
opcode OpSubstr<0x7f, 1>(self, vm) {
let [start, end] = vm.dstack.pop_items()?;
let data = vm.dstack.pop()?;
let [start, end] = i32s_to_usizes([start, end])?;
let substr = substring(&data, start, end)?;
vm.dstack.push(substr.into())
}
opcode OpLeft<0x80, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}")))
opcode OpRight<0x81, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}")))
opcode OpSize<0x82, 1>(self, vm) {
match vm.dstack.last() {
Some(last) => {
vm.dstack.push_item(i64::try_from(last.len()).map_err(|e| TxScriptError::NumberTooBig(e.to_string()))?)?;
Ok(())
},
None => Err(TxScriptError::InvalidStackOperation(1, 0))
}
}
opcode OpInvert<0x83, 1>(self, vm) {
let data = vm.dstack.pop()?;
let r: StackEntry = data.into_iter().map(|b| !b).collect();
vm.dstack.push(r)
}
opcode OpAnd<0x84, 1>(self, vm) {
let b = vm.dstack.pop()?;
let a = vm.dstack.pop()?;
if a.len() != b.len() {
return Err(TxScriptError::InvalidState("AND operands must be of equal length".to_string()));
}
let r: StackEntry = a.into_iter().zip(b.into_iter()).map(|(a_byte, b_byte)| a_byte & b_byte).collect();
vm.dstack.push(r)
}
opcode OpOr<0x85, 1>(self, vm) {
let b = vm.dstack.pop()?;
let a = vm.dstack.pop()?;
if a.len() != b.len() {
return Err(TxScriptError::InvalidState("OR operands must be of equal length".to_string()));
}
let r: StackEntry = a.into_iter().zip(b.into_iter()).map(|(a_byte, b_byte)| a_byte | b_byte).collect();
vm.dstack.push(r)
}
opcode OpXor<0x86, 1>(self, vm) {
let b = vm.dstack.pop()?;
let a = vm.dstack.pop()?;
if a.len() != b.len() {
return Err(TxScriptError::InvalidState("XOR operands must be of equal length".to_string()));
}
let r: StackEntry = a.into_iter().zip(b.into_iter()).map(|(a_byte, b_byte)| a_byte ^ b_byte).collect();
vm.dstack.push(r)
}
opcode OpEqual<0x87, 1>(self, vm) {
match vm.dstack.len() >= 2 {
true => {
let pair = vm.dstack.split_off(vm.dstack.len() - 2);
match pair[0] == pair[1] {
true => vm.dstack.push(([1]).as_slice().into()),
false => vm.dstack.push(([]).as_slice().into()),
}
}
false => Err(TxScriptError::InvalidStackOperation(2, vm.dstack.len()))
}
}
opcode OpEqualVerify<0x88, 1>(self, vm) {
match vm.dstack.len() >= 2 {
true => {
let pair = vm.dstack.split_off(vm.dstack.len() - 2);
match pair[0] == pair[1] {
true => Ok(()),
false => Err(TxScriptError::VerifyError),
}
}
false => Err(TxScriptError::InvalidStackOperation(2, vm.dstack.len()))
}
}
opcode OpReserved1<0x89, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}")))
opcode OpReserved2<0x8a, 1>(self, vm) Err(TxScriptError::OpcodeReserved(format!("{self:?}")))
opcode Op1Add<0x8b, 1>(self, vm) {
let [value]: [i64; 1] = vm.dstack.pop_items()?;
let r = value.checked_add(1).ok_or_else(|| TxScriptError::NumberTooBig("Result of addition exceeds 64-bit signed integer range".to_string()))?;
vm.dstack.push_item(r)?;
Ok(())
}
opcode Op1Sub<0x8c, 1>(self, vm) {
let [value]: [i64; 1] = vm.dstack.pop_items()?;
let r = value.checked_sub(1).ok_or_else(|| TxScriptError::NumberTooBig("Result of subtraction exceeds 64-bit signed integer range".to_string()))?;
vm.dstack.push_item(r)?;
Ok(())
}
opcode Op2Mul<0x8d, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}")))
opcode Op2Div<0x8e, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}")))
opcode OpNegate<0x8f, 1>(self, vm) {
let [value]: [i64; 1] = vm.dstack.pop_items()?;
let r = value.checked_neg().ok_or_else(|| TxScriptError::NumberTooBig("Negation result exceeds 64-bit signed integer range".to_string()))?;
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpAbs<0x90, 1>(self, vm) {
let [ value ]: [i64; 1] = vm.dstack.pop_items()?;
let r = value.checked_abs().ok_or_else(|| TxScriptError::NumberTooBig("Absolute value exceeds 64-bit signed integer range".to_string()))?;
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpNot<0x91, 1>(self, vm) {
let [ m ]: [i64; 1] = vm.dstack.pop_items()?;
let r = (m == 0) as i64;
vm.dstack.push_item(r)?;
Ok(())
}
opcode Op0NotEqual<0x92, 1>(self, vm) {
let [ m ]: [i64; 1] = vm.dstack.pop_items()?;
let r = (m != 0) as i64;
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpAdd<0x93, 1>(self, vm) {
let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?;
let r = a.checked_add(b).ok_or_else(|| TxScriptError::NumberTooBig("Sum exceeds 64-bit signed integer range".to_string()))?;
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpSub<0x94, 1>(self, vm) {
let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?;
let r = a.checked_sub(b).ok_or_else(|| TxScriptError::NumberTooBig("Difference exceeds 64-bit signed integer range".to_string()))?;
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpMul<0x95, 1>(self, vm) {
let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?;
let r = a.checked_mul(b).ok_or_else(|| TxScriptError::NumberTooBig("Product exceeds 64-bit signed integer range".to_string()))?;
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpDiv<0x96, 1>(self, vm) {
let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?;
let r = a.checked_div(b).ok_or_else(|| TxScriptError::InvalidState("Quotient exceeds 64-bit signed integer range, or there was a division by zero".to_string()))?;
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpMod<0x97, 1>(self, vm) {
let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?;
let r = a.checked_rem(b).ok_or_else(|| TxScriptError::InvalidState("Illegal modulo by zero".to_string()))?;
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpLShift<0x98, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}")))
opcode OpRShift<0x99, 1>(self, vm) Err(TxScriptError::OpcodeDisabled(format!("{self:?}")))
opcode OpBoolAnd<0x9a, 1>(self, vm) {
let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?;
let r = ((a != 0) && (b != 0)) as i64;
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpBoolOr<0x9b, 1>(self, vm) {
let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?;
let r = ((a != 0) || (b != 0)) as i64;
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpNumEqual<0x9c, 1>(self, vm) {
let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?;
let r = (a == b) as i64;
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpNumEqualVerify<0x9d, 1>(self, vm) {
let [a,b]: [i64; 2] = vm.dstack.pop_items()?;
match a == b {
true => Ok(()),
false => Err(TxScriptError::VerifyError)
}
}
opcode OpNumNotEqual<0x9e, 1>(self, vm) {
let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?;
let r = (a != b) as i64;
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpLessThan<0x9f, 1>(self, vm) {
let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?;
let r = (a < b) as i64;
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpGreaterThan<0xa0, 1>(self, vm) {
let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?;
let r = (a > b) as i64;
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpLessThanOrEqual<0xa1, 1>(self, vm) {
let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?;
let r = (a <= b) as i64;
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpGreaterThanOrEqual<0xa2, 1>(self, vm) {
let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?;
let r = (a >= b) as i64;
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpMin<0xa3, 1>(self, vm) {
let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?;
let r = a.min(b);
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpMax<0xa4, 1>(self, vm) {
let [ a, b ]: [i64; 2] = vm.dstack.pop_items()?;
let r = a.max(b);
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpWithin<0xa5, 1>(self, vm) {
let [ x, l, u ]: [i64; 3] = vm.dstack.pop_items()?;
let r = (x >= l && x < u) as i64;
vm.dstack.push_item(r)?;
Ok(())
}
opcode OpZkPrecompile<0xa6, 1>(self, vm) {
let tag = parse_tag(&mut vm.dstack)?;
vm.consume_script_units(tag.cost())?;
verify_zk(tag, &mut vm.dstack, &mut vm.runtime_resource_meter)?;
vm.dstack.push_item(true)?;
Ok(())
}
opcode OpBlake2bWithKey<0xa7, 1>(self, vm) {
let [data, key] = vm.dstack.pop_raw()?;
if key.len() > blake2b_simd::KEYBYTES {
return Err(TxScriptError::ElementTooBig(key.len(), blake2b_simd::KEYBYTES))
}
vm.consume_script_units(HashOpcodePricing::Blake2b.script_units_for_data(data.len()))?;
let hash = Params::new().hash_length(32).key(&key).to_state().update(&data).finalize();
vm.dstack.push(hash.as_bytes().into())
}
opcode OpSHA256<0xa8, 1>(self, vm) {
let [last] = vm.dstack.pop_raw()?;
vm.consume_script_units(HashOpcodePricing::Sha256.script_units_for_data(last.len()))?;
let mut hasher = Sha256::new();
hasher.update(last);
vm.dstack.push(hasher.finalize().as_slice().into())
}
opcode OpCheckMultiSigECDSA<0xa9, 1>(self, vm) {
vm.op_check_multisig_schnorr_or_ecdsa(true)
}
opcode OpBlake2b<0xaa, 1>(self, vm) {
let [last] = vm.dstack.pop_raw()?;
vm.consume_script_units(HashOpcodePricing::Blake2b.script_units_for_data(last.len()))?;
let hash = Params::new().hash_length(32).to_state().update(&last).finalize();
vm.dstack.push(hash.as_bytes().into())
}
opcode OpCheckSigECDSA<0xab, 1>(self, vm) {
let [mut sig, key] = vm.dstack.pop_raw()?;
match sig.pop() {
Some(typ) => {
let hash_type = SigHashType::from_u8(typ).map_err(|e| TxScriptError::InvalidSigHashType(typ))?;
match vm.check_ecdsa_signature(hash_type, key.as_slice(), sig.as_slice()) {
Ok(valid) => {
vm.dstack.push_item(valid)?;
Ok(())
},
Err(e) => {
Err(e)
}
}
}
None => {
vm.dstack.push_item(false)?;
Ok(())
}
}
}
opcode OpCheckSig<0xac, 1>(self, vm) {
let [mut sig, key] = vm.dstack.pop_raw()?;
match sig.pop() {
Some(typ) => {
let hash_type = SigHashType::from_u8(typ).map_err(|e| TxScriptError::InvalidSigHashType(typ))?;
match vm.check_schnorr_signature(hash_type, key.as_slice(), sig.as_slice()) {
Ok(valid) => {
vm.dstack.push_item(valid)?;
Ok(())
},
Err(e) => {
Err(e)
}
}
}
None => {
vm.dstack.push_item(false)?;
Ok(())
}
}
}
opcode OpCheckSigVerify<0xad, 1>(self, vm) {
OpCheckSig{data: self.data.clone()}.execute(vm)?;
let [valid]: [bool; 1] = vm.dstack.pop_items()?;
match valid {
true => Ok(()),
false => Err(TxScriptError::VerifyError)
}
}
opcode OpCheckMultiSig<0xae, 1>(self, vm) {
vm.op_check_multisig_schnorr_or_ecdsa(false)
}
opcode OpCheckMultiSigVerify<0xaf, 1>(self, vm) {
OpCheckMultiSig{data: self.data.clone()}.execute(vm)?;
let [valid]: [bool; 1] = vm.dstack.pop_items()?;
match valid {
true => Ok(()),
false => Err(TxScriptError::VerifyError)
}
}
opcode OpCheckLockTimeVerify<0xb0, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput {input, tx, ..} => {
let [mut lock_time_bytes] = vm.dstack.pop_raw()?;
if lock_time_bytes.len() > 8 {
return Err(TxScriptError::NumberTooBig(format!("lockTime value represented as {lock_time_bytes:x?} is longer then 8 bytes")))
}
lock_time_bytes.resize(8, 0);
let stack_lock_time = u64::from_le_bytes(lock_time_bytes.as_slice().try_into().expect("checked vector size"));
if !(
(tx.tx().lock_time < LOCK_TIME_THRESHOLD && stack_lock_time < LOCK_TIME_THRESHOLD) ||
(tx.tx().lock_time >= LOCK_TIME_THRESHOLD && stack_lock_time >= LOCK_TIME_THRESHOLD)
){
return Err(TxScriptError::UnsatisfiedLockTime(format!("mismatched locktime types -- tx locktime {}, stack locktime {}", tx.tx().lock_time, stack_lock_time)))
}
if stack_lock_time > tx.tx().lock_time {
return Err(TxScriptError::UnsatisfiedLockTime(format!("locktime requirement not satisfied -- locktime is greater than the transaction locktime: {} > {}", stack_lock_time, tx.tx().lock_time)))
}
if input.sequence == MAX_TX_IN_SEQUENCE_NUM {
return Err(TxScriptError::UnsatisfiedLockTime("transaction input is finalized".to_string()));
}
Ok(())
}
_ => Err(TxScriptError::InvalidSource("LockTimeVerify only applies to transaction inputs".to_string()))
}
}
opcode OpCheckSequenceVerify<0xb1, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput {input, tx, ..} => {
let [mut sequence_bytes] = vm.dstack.pop_raw()?;
if sequence_bytes.len() > 8 {
return Err(TxScriptError::NumberTooBig(format!("lockTime value represented as {sequence_bytes:x?} is longer then 8 bytes")))
}
sequence_bytes.resize(8, 0);
let stack_sequence = u64::from_le_bytes(sequence_bytes.as_slice().try_into().expect("ensured size checks"));
if stack_sequence & SEQUENCE_LOCK_TIME_DISABLED != 0 {
return Ok(());
}
if input.sequence & SEQUENCE_LOCK_TIME_DISABLED != 0 {
return Err(TxScriptError::UnsatisfiedLockTime(format!("transaction sequence has sequence locktime disabled bit set: {:#x}", input.sequence)));
}
if (stack_sequence & SEQUENCE_LOCK_TIME_MASK) > (input.sequence & SEQUENCE_LOCK_TIME_MASK) {
return Err(TxScriptError::UnsatisfiedLockTime(format!("locktime requirement not satisfied -- locktime is greater than the transaction locktime: {} > {}", stack_sequence & SEQUENCE_LOCK_TIME_MASK, input.sequence & SEQUENCE_LOCK_TIME_MASK)))
}
Ok(())
}
_ => Err(TxScriptError::InvalidSource("LockTimeVerify only applies to transaction inputs".to_string()))
}
}
opcode OpTxVersion<0xb2, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
push_number(tx.tx().version as i64, vm)
},
_ => Err(TxScriptError::InvalidSource("OpTxVersion only applies to transaction inputs".to_string()))
}
}
opcode OpTxInputCount<0xb3, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
push_number(tx.inputs().len() as i64, vm)
},
_ => Err(TxScriptError::InvalidSource("OpInputCount only applies to transaction inputs".to_string()))
}
}
opcode OpTxOutputCount<0xb4, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
push_number(tx.outputs().len() as i64, vm)
},
_ => Err(TxScriptError::InvalidSource("OpOutputCount only applies to transaction inputs".to_string()))
}
}
opcode OpTxLockTime<0xb5, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
push_number(tx.tx().lock_time as i64, vm)
},
_ => Err(TxScriptError::InvalidSource("OpTxLockTime only applies to transaction inputs".to_string()))
}
}
opcode OpTxSubnetId<0xb6, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
push_data(tx.tx().subnetwork_id.into(), vm)
},
_ => Err(TxScriptError::InvalidSource("OpTxSubnetId only applies to transaction inputs".to_string()))
}
}
opcode OpTxGas<0xb7, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
push_number(tx.tx().gas as i64, vm)
},
_ => Err(TxScriptError::InvalidSource("OpTxGas only applies to transaction inputs".to_string()))
}
}
opcode OpTxPayloadSubstr<0xb8, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [start, end] = i32s_to_usizes(vm.dstack.pop_items()?)?;
let substr = substring(&tx.tx().payload, start, end)?;
push_data(substr, vm)
},
_ => Err(TxScriptError::InvalidSource("OpTxPayloadSubstr only applies to transaction inputs".to_string()))
}
}
opcode OpTxInputIndex<0xb9, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{idx, ..} => {
push_number(idx as i64, vm)
},
_ => Err(TxScriptError::InvalidSource("OpInputIndex only applies to transaction inputs".to_string()))
}
}
opcode OpOutpointTxId<0xba, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx]: [i32; 1] = vm.dstack.pop_items()?;
let idx = i32_to_usize(idx)?;
let input = tx.inputs().get(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?;
push_data(input.previous_outpoint.transaction_id.as_bytes().into(), vm)
},
_ => Err(TxScriptError::InvalidSource("OpOutpointTxId only applies to transaction inputs".to_string()))
}
}
opcode OpOutpointIndex<0xbb, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx]: [i32; 1] = vm.dstack.pop_items()?;
let idx = i32_to_usize(idx)?;
let input = tx.inputs().get(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?;
push_number(input.previous_outpoint.index as i64, vm)
},
_ => Err(TxScriptError::InvalidSource("OpOutpointIndex only applies to transaction inputs".to_string()))
}
}
opcode OpTxInputScriptSigSubstr<0xbc, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx, start, end] = i32s_to_usizes(vm.dstack.pop_items()?)?;
let input = tx.inputs().get(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?;
let substr = substring(&input.signature_script, start, end)?;
push_data(substr, vm)
},
_ => Err(TxScriptError::InvalidSource("OpTxInputScriptSigSubstr only applies to transaction inputs".to_string()))
}
}
opcode OpTxInputSeq<0xbd, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx]: [i32; 1] = vm.dstack.pop_items()?;
let idx = i32_to_usize(idx)?;
let input = tx.inputs().get(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?;
push_data(input.sequence.to_le_bytes().into(), vm)
},
_ => Err(TxScriptError::InvalidSource("OpTxInputSeq only applies to transaction inputs".to_string()))
}
}
opcode OpTxInputAmount<0xbe, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx]: [i32; 1] = vm.dstack.pop_items()?;
let utxo = usize::try_from(idx).ok()
.and_then(|idx| tx.utxo(idx))
.ok_or_else(|| TxScriptError::InvalidInputIndex(idx, tx.inputs().len()))?;
push_number(utxo.amount.try_into().map_err(|e: TryFromIntError| TxScriptError::NumberTooBig(e.to_string()))?, vm)
},
_ => Err(TxScriptError::InvalidSource("OpInputAmount only applies to transaction inputs".to_string()))
}
}
opcode OpTxInputSpk<0xbf, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx]: [i32; 1] = vm.dstack.pop_items()?;
let utxo = usize::try_from(idx).ok()
.and_then(|idx| tx.utxo(idx))
.ok_or_else(|| TxScriptError::InvalidInputIndex(idx, tx.inputs().len()))?;
vm.dstack.push(utxo.script_public_key.to_bytes().into())
},
_ => Err(TxScriptError::InvalidSource("OpInputSpk only applies to transaction inputs".to_string()))
}
}
opcode OpTxInputDaaScore<0xc0, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx]: [i32; 1] = vm.dstack.pop_items()?;
let idx = i32_to_usize(idx)?;
let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?;
push_number(utxo.block_daa_score as i64, vm)
},
_ => Err(TxScriptError::InvalidSource("OpTxInputDaaScore only applies to transaction inputs".to_string()))
}
}
opcode OpTxInputIsCoinbase<0xc1, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx]: [i32; 1] = vm.dstack.pop_items()?;
let idx = i32_to_usize(idx)?;
let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?;
push_number(utxo.is_coinbase as i64, vm)
},
_ => Err(TxScriptError::InvalidSource("OpTxInputIsCoinbase only applies to transaction inputs".to_string()))
}
}
opcode OpTxOutputAmount<0xc2, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx]: [i32; 1] = vm.dstack.pop_items()?;
let output = usize::try_from(idx).ok()
.and_then(|idx| tx.outputs().get(idx))
.ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.outputs().len()))?;
push_number(output.value.try_into().map_err(|e: TryFromIntError| TxScriptError::NumberTooBig(e.to_string()))?, vm)
},
_ => Err(TxScriptError::InvalidSource("OpTxOutputAmount only applies to transaction inputs".to_string()))
}
}
opcode OpTxOutputSpk<0xc3, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx]: [i32; 1] = vm.dstack.pop_items()?;
let output = usize::try_from(idx).ok()
.and_then(|idx| tx.outputs().get(idx))
.ok_or_else(|| TxScriptError::InvalidOutputIndex(idx, tx.outputs().len()))?;
vm.dstack.push(output.script_public_key.to_bytes().into())
},
_ => Err(TxScriptError::InvalidSource("OpTxOutputSpk only applies to transaction inputs".to_string()))
}
}
opcode OpTxPayloadLen<0xc4, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
push_number(tx.tx().payload.len() as i64, vm)
},
_ => Err(TxScriptError::InvalidSource("OpTxPayloadLen only applies to transaction inputs".to_string()))
}
}
opcode OpTxInputSpkLen<0xc5, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx]: [i32; 1] = vm.dstack.pop_items()?;
let idx = i32_to_usize(idx)?;
let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?;
let len = utxo.script_public_key.to_bytes().len() as i64;
push_number(len, vm)
},
_ => Err(TxScriptError::InvalidSource("OpTxInputSpkLen only applies to transaction inputs".to_string()))
}
}
opcode OpTxInputSpkSubstr<0xc6, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx, start, end] = i32s_to_usizes(vm.dstack.pop_items()?)?;
let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?;
let spk_bytes = utxo.script_public_key.to_bytes();
let substr = substring(&spk_bytes, start, end)?;
push_data(substr, vm)
},
_ => Err(TxScriptError::InvalidSource("OpTxInputSpkSubstr only applies to transaction inputs".to_string()))
}
}
opcode OpTxOutputSpkLen<0xc7, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx]: [i32; 1] = vm.dstack.pop_items()?;
let idx = i32_to_usize(idx)?;
let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx as i32, tx.outputs().len()))?;
let len = output.script_public_key.to_bytes().len() as i64;
push_number(len, vm)
},
_ => Err(TxScriptError::InvalidSource("OpTxOutputSpkLen only applies to transaction inputs".to_string()))
}
}
opcode OpTxOutputSpkSubstr<0xc8, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx, start, end] = i32s_to_usizes(vm.dstack.pop_items()?)?;
let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx as i32, tx.outputs().len()))?;
let spk_bytes = output.script_public_key.to_bytes();
let substr = substring(&spk_bytes, start, end)?;
push_data(substr, vm)
},
_ => Err(TxScriptError::InvalidSource("OpTxOutputSpkSubstr only applies to transaction inputs".to_string()))
}
}
opcode OpTxInputScriptSigLen<0xc9, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx]: [i32; 1] = vm.dstack.pop_items()?;
let idx = i32_to_usize(idx)?;
let input = tx.inputs().get(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?;
let len = input.signature_script.len() as i64;
push_number(len, vm)
},
_ => Err(TxScriptError::InvalidSource("OpTxInputScriptSigLen only applies to transaction inputs".to_string()))
}
}
opcode OpUnknown202<0xca, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpAuthOutputCount<0xcb, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [input_idx]: [i32; 1] = vm.dstack.pop_items()?;
let input_idx = i32_to_usize(input_idx)?;
if input_idx >= tx.inputs().len() {
return Err(TxScriptError::InvalidInputIndex(input_idx.try_into().expect("casted above"), tx.inputs().len()));
}
let count = vm.covenants_ctx.num_auth_outputs(input_idx);
push_number(count as i64, vm)
},
_ => Err(TxScriptError::InvalidSource("OpAuthOutputCount only applies to transaction inputs".to_string()))
}
}
opcode OpAuthOutputIdx<0xcc, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [input_idx, k] = i32s_to_usizes(vm.dstack.pop_items()?)?;
if input_idx >= tx.inputs().len() {
return Err(TxScriptError::InvalidInputIndex(input_idx.try_into().expect("casted above"), tx.inputs().len()));
}
let output_idx = vm.covenants_ctx.auth_output_index(input_idx, k)?;
push_number(output_idx as i64, vm)
},
_ => Err(TxScriptError::InvalidSource("OpAuthOutputIdx only applies to transaction inputs".to_string()))
}
}
opcode OpNum2Bin<0xcd, 1>(self, vm) {
let [size]: [i32; 1] = vm.dstack.pop_items()?;
let size = i32_to_usize(size)?;
if size > 8 {
return Err(TxScriptError::NotMinimalData(format!("NUM2BIN target size {size} exceeds 8 bytes")));
}
let [num]: [i64; 1] = vm.dstack.pop_items()?;
let r = serialize_i64(num, Some(size))?;
vm.dstack.push(r)
}
opcode OpBin2Num<0xce, 1>(self, vm) {
let [num]: [i64; 1] = vm.dstack.pop_items()?;
push_number(num, vm)
}
opcode OpInputCovenantId<0xcf, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx]: [i32; 1] = vm.dstack.pop_items()?;
let idx = i32_to_usize(idx)?;
let utxo = tx.utxo(idx).ok_or_else(|| TxScriptError::InvalidInputIndex(idx as i32, tx.inputs().len()))?;
let covenant_id = utxo.covenant_id.unwrap_or(ZERO_HASH);
push_data(covenant_id.as_bytes().into(), vm)
},
_ => Err(TxScriptError::InvalidSource("OpInputCovenantId only applies to transaction inputs".to_string()))
}
}
opcode OpCovInputCount<0xd0, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [covenant_id]: [Hash; 1] = vm.dstack.pop_items()?;
let count = vm.covenants_ctx.num_covenant_inputs(covenant_id);
push_number(count as i64, vm)
},
_ => Err(TxScriptError::InvalidSource("OpCovInputCount only applies to transaction inputs".to_string()))
}
}
opcode OpCovInputIdx<0xd1, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [k]: [i32; 1] = vm.dstack.pop_items()?;
let k = i32_to_usize(k)?;
let [covenant_id]: [Hash; 1] = vm.dstack.pop_items()?;
let idx = vm.covenants_ctx.covenant_input_index(covenant_id, k)?;
push_number(idx as i64, vm)
},
_ => Err(TxScriptError::InvalidSource("OpCovInputIdx only applies to transaction inputs".to_string()))
}
}
opcode OpCovOutputCount<0xd2, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [covenant_id]: [Hash; 1] = vm.dstack.pop_items()?;
let count = vm.covenants_ctx.num_covenant_outputs(covenant_id);
push_number(count as i64, vm)
},
_ => Err(TxScriptError::InvalidSource("OpCovOutputCount only applies to transaction inputs".to_string()))
}
}
opcode OpCovOutputIdx<0xd3, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [k]: [i32; 1] = vm.dstack.pop_items()?;
let k = i32_to_usize(k)?;
let [covenant_id]: [Hash; 1] = vm.dstack.pop_items()?;
let idx = vm.covenants_ctx.covenant_output_index(covenant_id, k)?;
push_number(idx as i64, vm)
},
_ => Err(TxScriptError::InvalidSource("OpCovOutputIdx only applies to transaction inputs".to_string()))
}
}
opcode OpChainblockSeqCommit<0xd4, 1>(self, vm) {
let Some(seq_commit_accessor) = vm.ctx.seq_commit_accessor else {
return Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
};
let [block]: [Hash; 1] = vm.dstack.pop_items()?; match seq_commit_accessor.is_chain_ancestor_from_pov(block) {
None => return Err(TxScriptError::BlockAlreadyPruned(block.to_string())),
Some(false) => return Err(TxScriptError::BlockNotSelected(block.to_string())),
Some(true) => {}
};
let commitment = seq_commit_accessor.seq_commitment_within_depth(block)
.ok_or_else(|| TxScriptError::BlockIsTooDeep(block.to_string()))?;
vm.dstack.push_item(commitment)?;
Ok(())
}
opcode OpOutputCovenantId<0xd5, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx]: [i32; 1] = vm.dstack.pop_items()?;
let idx = i32_to_usize(idx)?;
let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx as i32, tx.outputs().len()))?;
let covenant_id = output.covenant.map(|c| c.covenant_id).unwrap_or(ZERO_HASH);
push_data(covenant_id.as_bytes().into(), vm)
},
_ => Err(TxScriptError::InvalidSource("OpOutputCovenantId only applies to transaction inputs".to_string()))
}
}
opcode OpOutputAuthorizingInput<0xd6, 1>(self, vm) {
match vm.script_source {
ScriptSource::TxInput{tx, ..} => {
let [idx]: [i32; 1] = vm.dstack.pop_items()?;
let idx = i32_to_usize(idx)?;
let output = tx.outputs().get(idx).ok_or_else(|| TxScriptError::InvalidOutputIndex(idx as i32, tx.outputs().len()))?;
let auth_input_idx = output.covenant.as_ref()
.map(|c| c.authorizing_input as i64)
.unwrap_or(-1i64);
push_number(auth_input_idx, vm)
},
_ => Err(TxScriptError::InvalidSource("OpOutputAuthorizingInput only applies to transaction inputs".to_string()))
}
}
opcode OpCheckSigFromStack<0xd7, 1>(self, vm) {
let [signature, msg_hash, pubkey] = vm.dstack.pop_raw()?;
let msg_hash = Hash::try_from(msg_hash.as_slice()).map_err(|_| TxScriptError::InvalidState("message hash must be 32 bytes".to_string()))?;
let is_valid = vm.check_schnorr_signature_with_msg_hash(&pubkey, &signature, |_| msg_hash)?;
vm.dstack.push_item(is_valid)
}
opcode OpCheckSigFromStackECDSA<0xd8, 1>(self, vm) {
let [signature, msg_hash, pubkey] = vm.dstack.pop_raw()?;
let msg_hash = Hash::try_from(msg_hash.as_slice()).map_err(|_| TxScriptError::InvalidState("message hash must be 32 bytes".to_string()))?;
let is_valid = vm.check_ecdsa_signature_with_msg_hash(&pubkey, &signature, |_| msg_hash)?;
vm.dstack.push_item(is_valid)
}
opcode OpBlake3<0xd9, 1>(self, vm) {
let [data] = vm.dstack.pop_raw()?;
vm.consume_script_units(HashOpcodePricing::Blake3.script_units_for_data(data.len()))?;
let hash = blake3::hash(&data);
vm.dstack.push(hash.as_slice().into())
}
opcode OpBlake3WithKey<0xda, 1>(self, vm) {
let [data, key] = vm.dstack.pop_raw()?;
let key: &[u8; blake3::KEY_LEN] = key.as_slice().try_into().map_err(|_| {
TxScriptError::MalformedPush(blake3::KEY_LEN, key.len())
})?;
vm.consume_script_units(HashOpcodePricing::Blake3.script_units_for_data(data.len()))?;
let hash = blake3::keyed_hash(key, &data);
vm.dstack.push(hash.as_slice().into())
}
opcode OpUnknown219<0xdb, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown220<0xdc, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown221<0xdd, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown222<0xde, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown223<0xdf, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown224<0xe0, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown225<0xe1, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown226<0xe2, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown227<0xe3, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown228<0xe4, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown229<0xe5, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown230<0xe6, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown231<0xe7, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown232<0xe8, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown233<0xe9, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown234<0xea, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown235<0xeb, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown236<0xec, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown237<0xed, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown238<0xee, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown239<0xef, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown240<0xf0, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown241<0xf1, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown242<0xf2, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown243<0xf3, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown244<0xf4, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown245<0xf5, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown246<0xf6, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown247<0xf7, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown248<0xf8, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown249<0xf9, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpSmallInteger<0xfa, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpPubKeys<0xfb, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpUnknown252<0xfc, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpPubKeyHash<0xfd, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpPubKey<0xfe, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
opcode OpInvalidOpCode<0xff, 1>(self, vm) Err(TxScriptError::InvalidOpcode(format!("{self:?}")))
}
#[allow(clippy::borrowed_box)]
pub fn to_small_int<T: VerifiableTransaction, Reused: SigHashReusedValues>(opcode: &Box<dyn OpCodeImplementation<T, Reused>>) -> u8 {
let value = opcode.value();
if value == codes::OpFalse {
return 0;
}
assert!((codes::OpTrue..codes::Op16).contains(&value), "expected op codes between from the list of Op0 to Op16");
value - (codes::OpTrue - 1)
}
#[cfg(test)]
mod test {
use crate::caches::Cache;
use crate::data_stack::{Stack, StackEntry};
use crate::opcodes::{OpCodeExecution, OpCodeImplementation};
use crate::{
EngineContext, EngineFlags, LOCK_TIME_THRESHOLD, TxScriptEngine, TxScriptError, opcodes, pay_to_address_script, script_to_str,
};
use kaspa_addresses::{Address, Prefix, Version};
use kaspa_consensus_core::constants::{SOMPI_PER_KASPA, TX_VERSION};
use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync;
use kaspa_consensus_core::subnets::SUBNETWORK_ID_NATIVE;
use kaspa_consensus_core::tx::{
PopulatedTransaction, ScriptPublicKey, Transaction, TransactionInput, TransactionOutpoint, TransactionOutput, UtxoEntry,
VerifiableTransaction,
};
struct TestCase<'a> {
init: Vec<Vec<u8>>,
code: Box<dyn OpCodeImplementation<PopulatedTransaction<'a>, SigHashReusedValuesUnsync>>,
dstack: Vec<Vec<u8>>,
}
struct ErrorTestCase<'a> {
init: Vec<Vec<u8>>,
code: Box<dyn OpCodeImplementation<PopulatedTransaction<'a>, SigHashReusedValuesUnsync>>,
error: TxScriptError,
}
fn run_success_test_cases(tests: Vec<TestCase>) {
run_success_test_cases_with_flags(tests, Default::default());
}
fn run_success_test_cases_with_flags(tests: Vec<TestCase>, flags: EngineFlags) {
let cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&cache).with_reused(&reused_values);
for TestCase { init, code, dstack } in tests {
let init: Stack = init.into();
let dstack = dstack.into();
let mut vm = TxScriptEngine::new(ctx, flags);
vm.dstack = init.clone();
code.execute(&mut vm).unwrap_or_else(|_| panic!("Opcode {} should not fail", code.value()));
assert_eq!(vm.dstack, dstack, "OpCode {} Pushed wrong value", code.value());
}
}
fn run_error_test_cases(tests: Vec<ErrorTestCase>) {
run_error_test_cases_with_flags(tests, Default::default());
}
fn run_error_test_cases_with_flags(tests: Vec<ErrorTestCase>, flags: EngineFlags) {
let cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&cache).with_reused(&reused_values);
for ErrorTestCase { init, code, error } in tests {
let mut vm = TxScriptEngine::new(ctx, flags);
vm.dstack = init.clone().into();
assert_eq!(
code.execute(&mut vm)
.expect_err(format!("Opcode {} should have errored (init: {:?})", code.value(), init.clone()).as_str()),
error,
"Opcode {} returned wrong error {:?}",
code.value(),
init
);
}
}
#[test]
fn test_opcode_disabled() {
let tests: Vec<Box<dyn OpCodeImplementation<PopulatedTransaction, SigHashReusedValuesUnsync>>> = vec![
opcodes::OpLeft::empty().expect("Should accept empty"),
opcodes::OpRight::empty().expect("Should accept empty"),
opcodes::Op2Mul::empty().expect("Should accept empty"),
opcodes::Op2Div::empty().expect("Should accept empty"),
opcodes::OpLShift::empty().expect("Should accept empty"),
opcodes::OpRShift::empty().expect("Should accept empty"),
];
let cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&cache).with_reused(&reused_values);
let mut vm = TxScriptEngine::new(ctx, Default::default());
for pop in tests {
match pop.execute(&mut vm) {
Err(TxScriptError::OpcodeDisabled(_)) => {}
_ => panic!("Opcode {pop:?} should be disabled"),
}
}
}
#[test]
fn test_opcode_reserved() {
let tests: Vec<Box<dyn OpCodeImplementation<PopulatedTransaction, SigHashReusedValuesUnsync>>> = vec![
opcodes::OpReserved::empty().expect("Should accept empty"),
opcodes::OpVer::empty().expect("Should accept empty"),
opcodes::OpVerIf::empty().expect("Should accept empty"),
opcodes::OpVerNotIf::empty().expect("Should accept empty"),
opcodes::OpReserved1::empty().expect("Should accept empty"),
opcodes::OpReserved2::empty().expect("Should accept empty"),
];
let cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&cache).with_reused(&reused_values);
let mut vm = TxScriptEngine::new(ctx, Default::default());
for pop in tests {
match pop.execute(&mut vm) {
Err(TxScriptError::OpcodeReserved(_)) => {}
_ => panic!("Opcode {pop:?} should be disabled"),
}
}
}
#[test]
fn test_opcode_invalid() {
let tests: Vec<Box<dyn OpCodeImplementation<PopulatedTransaction, SigHashReusedValuesUnsync>>> = vec![
opcodes::OpUnknown202::empty().expect("Should accept empty"),
opcodes::OpUnknown219::empty().expect("Should accept empty"),
opcodes::OpUnknown220::empty().expect("Should accept empty"),
opcodes::OpUnknown221::empty().expect("Should accept empty"),
opcodes::OpUnknown222::empty().expect("Should accept empty"),
opcodes::OpUnknown223::empty().expect("Should accept empty"),
opcodes::OpUnknown224::empty().expect("Should accept empty"),
opcodes::OpUnknown225::empty().expect("Should accept empty"),
opcodes::OpUnknown226::empty().expect("Should accept empty"),
opcodes::OpUnknown227::empty().expect("Should accept empty"),
opcodes::OpUnknown228::empty().expect("Should accept empty"),
opcodes::OpUnknown229::empty().expect("Should accept empty"),
opcodes::OpUnknown230::empty().expect("Should accept empty"),
opcodes::OpUnknown231::empty().expect("Should accept empty"),
opcodes::OpUnknown232::empty().expect("Should accept empty"),
opcodes::OpUnknown233::empty().expect("Should accept empty"),
opcodes::OpUnknown234::empty().expect("Should accept empty"),
opcodes::OpUnknown235::empty().expect("Should accept empty"),
opcodes::OpUnknown236::empty().expect("Should accept empty"),
opcodes::OpUnknown237::empty().expect("Should accept empty"),
opcodes::OpUnknown238::empty().expect("Should accept empty"),
opcodes::OpUnknown239::empty().expect("Should accept empty"),
opcodes::OpUnknown240::empty().expect("Should accept empty"),
opcodes::OpUnknown241::empty().expect("Should accept empty"),
opcodes::OpUnknown242::empty().expect("Should accept empty"),
opcodes::OpUnknown243::empty().expect("Should accept empty"),
opcodes::OpUnknown244::empty().expect("Should accept empty"),
opcodes::OpUnknown245::empty().expect("Should accept empty"),
opcodes::OpUnknown246::empty().expect("Should accept empty"),
opcodes::OpUnknown247::empty().expect("Should accept empty"),
opcodes::OpUnknown248::empty().expect("Should accept empty"),
opcodes::OpUnknown249::empty().expect("Should accept empty"),
];
let cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&cache).with_reused(&reused_values);
let mut vm = TxScriptEngine::new(ctx, Default::default());
for pop in tests {
match pop.execute(&mut vm) {
Err(TxScriptError::InvalidOpcode(_)) => {}
_ => panic!("Opcode {pop:?} should be disabled"),
}
}
}
#[test]
fn test_opcode_to_string_without_data() {
let opcode: Box<dyn OpCodeImplementation<PopulatedTransaction, SigHashReusedValuesUnsync>> =
opcodes::OpTrue::empty().expect("Should accept empty");
assert_eq!(opcode.to_string(), "OpTrue");
}
#[test]
fn test_opcode_to_string_with_data() {
let opcode: Box<dyn OpCodeImplementation<PopulatedTransaction, SigHashReusedValuesUnsync>> =
opcodes::OpData2::new(vec![0xab, 0xcd]).expect("Should accept data");
assert_eq!(opcode.to_string(), "OpData2 0xabcd");
}
#[test]
fn test_script_builder_roundtrip_to_joined_opcode_string() {
use opcodes::codes::{OpBlake2b, OpCheckSig, OpDrop, OpDup, OpEqualVerify, OpVerify};
let mut builder = crate::script_builder::ScriptBuilder::new();
builder
.add_op(OpDup)
.unwrap()
.add_data(&[0x02, 0xab, 0xcd])
.unwrap()
.add_op(OpBlake2b)
.unwrap()
.add_data(b"kaspa")
.unwrap()
.add_op(OpEqualVerify)
.unwrap()
.add_i64(-1)
.unwrap()
.add_i64(16)
.unwrap()
.add_op(OpCheckSig)
.unwrap()
.add_data(&[0xde, 0xad, 0xbe, 0xef])
.unwrap()
.add_op(OpDrop)
.unwrap()
.add_op(OpVerify)
.unwrap();
let script = builder.drain();
assert_eq!(
script_to_str(&script).unwrap(),
"OpDup OpData3 0x02abcd OpBlake2b OpData5 0x6b61737061 OpEqualVerify Op1Negate Op16 OpCheckSig OpData4 0xdeadbeef OpDrop OpVerify"
);
}
#[test]
fn test_push_data() {
run_success_test_cases(vec![
TestCase { code: opcodes::OpFalse::empty().expect("Should accept empty"), dstack: vec![vec![]], init: Default::default() },
TestCase {
code: opcodes::OpData1::new([1u8; 1].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 1].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData2::new([1u8; 2].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 2].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData3::new([1u8; 3].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 3].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData4::new([1u8; 4].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 4].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData5::new([1u8; 5].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 5].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData6::new([1u8; 6].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 6].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData7::new([1u8; 7].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 7].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData8::new([1u8; 8].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 8].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData9::new([1u8; 9].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 9].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData10::new([1u8; 10].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 10].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData11::new([1u8; 11].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 11].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData12::new([1u8; 12].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 12].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData13::new([1u8; 13].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 13].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData14::new([1u8; 14].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 14].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData15::new([1u8; 15].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 15].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData16::new([1u8; 16].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 16].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData17::new([1u8; 17].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 17].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData18::new([1u8; 18].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 18].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData19::new([1u8; 19].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 19].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData20::new([1u8; 20].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 20].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData21::new([1u8; 21].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 21].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData22::new([1u8; 22].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 22].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData23::new([1u8; 23].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 23].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData24::new([1u8; 24].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 24].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData25::new([1u8; 25].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 25].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData26::new([1u8; 26].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 26].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData27::new([1u8; 27].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 27].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData28::new([1u8; 28].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 28].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData29::new([1u8; 29].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 29].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData30::new([1u8; 30].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 30].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData31::new([1u8; 31].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 31].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData32::new([1u8; 32].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 32].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData33::new([1u8; 33].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 33].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData34::new([1u8; 34].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 34].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData35::new([1u8; 35].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 35].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData36::new([1u8; 36].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 36].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData37::new([1u8; 37].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 37].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData38::new([1u8; 38].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 38].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData39::new([1u8; 39].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 39].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData40::new([1u8; 40].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 40].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData41::new([1u8; 41].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 41].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData42::new([1u8; 42].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 42].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData43::new([1u8; 43].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 43].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData44::new([1u8; 44].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 44].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData45::new([1u8; 45].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 45].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData46::new([1u8; 46].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 46].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData47::new([1u8; 47].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 47].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData48::new([1u8; 48].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 48].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData49::new([1u8; 49].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 49].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData50::new([1u8; 50].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 50].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData51::new([1u8; 51].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 51].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData52::new([1u8; 52].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 52].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData53::new([1u8; 53].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 53].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData54::new([1u8; 54].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 54].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData55::new([1u8; 55].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 55].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData56::new([1u8; 56].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 56].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData57::new([1u8; 57].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 57].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData58::new([1u8; 58].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 58].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData59::new([1u8; 59].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 59].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData60::new([1u8; 60].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 60].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData61::new([1u8; 61].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 61].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData62::new([1u8; 62].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 62].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData63::new([1u8; 63].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 63].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData64::new([1u8; 64].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 64].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData65::new([1u8; 65].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 65].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData66::new([1u8; 66].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 66].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData67::new([1u8; 67].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 67].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData68::new([1u8; 68].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 68].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData69::new([1u8; 69].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 69].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData70::new([1u8; 70].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 70].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData71::new([1u8; 71].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 71].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData72::new([1u8; 72].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 72].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData73::new([1u8; 73].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 73].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData74::new([1u8; 74].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 74].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpData75::new([1u8; 75].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 75].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpPushData1::new([1u8; 76].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 76].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpPushData2::new([1u8; 0x100].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 0x100].to_vec()],
init: Default::default(),
},
TestCase {
code: opcodes::OpPushData4::new([1u8; 0x10000].to_vec()).expect("Valid opcode"),
dstack: vec![[1u8; 0x10000].to_vec()],
init: Default::default(),
},
]);
}
#[test]
fn test_push_num() {
run_success_test_cases(vec![
TestCase {
code: opcodes::Op1Negate::empty().expect("Should accept empty"),
dstack: vec![vec![0x81]],
init: Default::default(),
},
TestCase { code: opcodes::Op1::empty().expect("Should accept empty"), dstack: vec![vec![1]], init: Default::default() },
TestCase { code: opcodes::Op2::empty().expect("Should accept empty"), dstack: vec![vec![2]], init: Default::default() },
TestCase { code: opcodes::Op3::empty().expect("Should accept empty"), dstack: vec![vec![3]], init: Default::default() },
TestCase { code: opcodes::Op4::empty().expect("Should accept empty"), dstack: vec![vec![4]], init: Default::default() },
TestCase { code: opcodes::Op5::empty().expect("Should accept empty"), dstack: vec![vec![5]], init: Default::default() },
TestCase { code: opcodes::Op6::empty().expect("Should accept empty"), dstack: vec![vec![6]], init: Default::default() },
TestCase { code: opcodes::Op7::empty().expect("Should accept empty"), dstack: vec![vec![7]], init: Default::default() },
TestCase { code: opcodes::Op8::empty().expect("Should accept empty"), dstack: vec![vec![8]], init: Default::default() },
TestCase { code: opcodes::Op9::empty().expect("Should accept empty"), dstack: vec![vec![9]], init: Default::default() },
TestCase { code: opcodes::Op10::empty().expect("Should accept empty"), dstack: vec![vec![10]], init: Default::default() },
TestCase { code: opcodes::Op11::empty().expect("Should accept empty"), dstack: vec![vec![11]], init: Default::default() },
TestCase { code: opcodes::Op12::empty().expect("Should accept empty"), dstack: vec![vec![12]], init: Default::default() },
TestCase { code: opcodes::Op13::empty().expect("Should accept empty"), dstack: vec![vec![13]], init: Default::default() },
TestCase { code: opcodes::Op14::empty().expect("Should accept empty"), dstack: vec![vec![14]], init: Default::default() },
TestCase { code: opcodes::Op15::empty().expect("Should accept empty"), dstack: vec![vec![15]], init: Default::default() },
TestCase { code: opcodes::Op16::empty().expect("Should accept empty"), dstack: vec![vec![16]], init: Default::default() },
]);
}
#[test]
fn test_uniary_num_ops() {
run_success_test_cases(vec![
TestCase { code: opcodes::Op1Add::empty().expect("Should accept empty"), init: vec![vec![]], dstack: vec![vec![1]] },
TestCase { code: opcodes::Op1Add::empty().expect("Should accept empty"), init: vec![vec![1]], dstack: vec![vec![2]] },
TestCase {
code: opcodes::Op1Add::empty().expect("Should accept empty"),
init: vec![vec![2, 1]],
dstack: vec![vec![3, 1]],
},
TestCase { code: opcodes::Op1Add::empty().expect("Should accept empty"), init: vec![vec![0x81]], dstack: vec![vec![]] },
TestCase { code: opcodes::Op1Sub::empty().expect("Should accept empty"), init: vec![vec![]], dstack: vec![vec![0x81]] },
TestCase { code: opcodes::Op1Sub::empty().expect("Should accept empty"), init: vec![vec![1]], dstack: vec![vec![]] },
TestCase { code: opcodes::Op1Sub::empty().expect("Should accept empty"), init: vec![vec![2]], dstack: vec![vec![1]] },
TestCase {
code: opcodes::Op1Sub::empty().expect("Should accept empty"),
init: vec![vec![3, 1]],
dstack: vec![vec![2, 1]],
},
TestCase { code: opcodes::OpNegate::empty().expect("Should accept empty"), init: vec![vec![]], dstack: vec![vec![]] },
TestCase { code: opcodes::OpNegate::empty().expect("Should accept empty"), init: vec![vec![1]], dstack: vec![vec![0x81]] },
TestCase { code: opcodes::OpNegate::empty().expect("Should accept empty"), init: vec![vec![0x81]], dstack: vec![vec![1]] },
TestCase {
code: opcodes::OpNegate::empty().expect("Should accept empty"),
init: vec![vec![3, 1]],
dstack: vec![vec![3, 0x81]],
},
TestCase { code: opcodes::OpAbs::empty().expect("Should accept empty"), init: vec![vec![]], dstack: vec![vec![]] },
TestCase { code: opcodes::OpAbs::empty().expect("Should accept empty"), init: vec![vec![3, 1]], dstack: vec![vec![3, 1]] },
TestCase {
code: opcodes::OpAbs::empty().expect("Should accept empty"),
init: vec![vec![3, 0x81]],
dstack: vec![vec![3, 1]],
},
TestCase { code: opcodes::OpAbs::empty().expect("Should accept empty"), init: vec![vec![1]], dstack: vec![vec![1]] },
TestCase { code: opcodes::OpAbs::empty().expect("Should accept empty"), init: vec![vec![0x81]], dstack: vec![vec![1]] },
TestCase {
code: opcodes::OpAbs::empty().expect("Should accept empty"),
init: vec![vec![1, 1, 0x82]],
dstack: vec![vec![1, 1, 2]],
},
TestCase { code: opcodes::OpNot::empty().expect("Should accept empty"), init: vec![vec![]], dstack: vec![vec![1]] },
TestCase { code: opcodes::OpNot::empty().expect("Should accept empty"), init: vec![vec![1]], dstack: vec![vec![]] },
TestCase { code: opcodes::OpNot::empty().expect("Should accept empty"), init: vec![vec![1, 2, 3]], dstack: vec![vec![]] },
TestCase { code: opcodes::Op0NotEqual::empty().expect("Should accept empty"), init: vec![vec![]], dstack: vec![vec![]] },
TestCase { code: opcodes::Op0NotEqual::empty().expect("Should accept empty"), init: vec![vec![1]], dstack: vec![vec![1]] },
TestCase { code: opcodes::Op0NotEqual::empty().expect("Should accept empty"), init: vec![vec![2]], dstack: vec![vec![1]] },
TestCase {
code: opcodes::Op0NotEqual::empty().expect("Should accept empty"),
init: vec![vec![1, 2, 3]],
dstack: vec![vec![1]],
},
]);
}
#[test]
fn test_binary_num_ops() {
run_success_test_cases(vec![
TestCase { code: opcodes::OpAdd::empty().expect("Should accept empty"), init: vec![vec![], vec![]], dstack: vec![vec![]] },
TestCase {
code: opcodes::OpAdd::empty().expect("Should accept empty"),
init: vec![vec![], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpAdd::empty().expect("Should accept empty"),
init: vec![vec![1], vec![]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpAdd::empty().expect("Should accept empty"),
init: vec![vec![1], vec![1]],
dstack: vec![vec![2]],
},
TestCase {
code: opcodes::OpAdd::empty().expect("Should accept empty"),
init: vec![vec![0x81], vec![1]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpAdd::empty().expect("Should accept empty"),
init: vec![vec![0x7f], vec![1]],
dstack: vec![vec![0x80, 0]],
},
TestCase {
code: opcodes::OpAdd::empty().expect("Should accept empty"),
init: vec![vec![0x80, 0], vec![0x80, 0]],
dstack: vec![vec![0, 1]],
},
TestCase {
code: opcodes::OpAdd::empty().expect("Should accept empty"),
init: vec![vec![0xff, 0], vec![1]],
dstack: vec![vec![0, 1]],
},
TestCase { code: opcodes::OpSub::empty().expect("Should accept empty"), init: vec![vec![], vec![]], dstack: vec![vec![]] },
TestCase {
code: opcodes::OpSub::empty().expect("Should accept empty"),
init: vec![vec![], vec![1]],
dstack: vec![vec![0x81]],
},
TestCase {
code: opcodes::OpSub::empty().expect("Should accept empty"),
init: vec![vec![1], vec![]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpSub::empty().expect("Should accept empty"),
init: vec![vec![1], vec![1]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpSub::empty().expect("Should accept empty"),
init: vec![vec![0x81], vec![1]],
dstack: vec![vec![0x82]],
},
TestCase {
code: opcodes::OpSub::empty().expect("Should accept empty"),
init: vec![vec![0x80, 0], vec![1]],
dstack: vec![vec![0x7f]],
},
TestCase {
code: opcodes::OpSub::empty().expect("Should accept empty"),
init: vec![vec![0, 1], vec![0x80, 0]],
dstack: vec![vec![0x80, 0]],
},
TestCase {
code: opcodes::OpSub::empty().expect("Should accept empty"),
init: vec![vec![0, 1], vec![1]],
dstack: vec![vec![0xff, 0]],
},
TestCase {
code: opcodes::OpMax::empty().expect("Should accept empty"),
init: vec![vec![0, 1], vec![1]],
dstack: vec![vec![0, 1]],
},
TestCase {
code: opcodes::OpMax::empty().expect("Should accept empty"),
init: vec![vec![1], vec![0, 1]],
dstack: vec![vec![0, 1]],
},
TestCase {
code: opcodes::OpMax::empty().expect("Should accept empty"),
init: vec![vec![0, 0x81], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpMin::empty().expect("Should accept empty"),
init: vec![vec![0, 1], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpMin::empty().expect("Should accept empty"),
init: vec![vec![1], vec![0, 1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpMin::empty().expect("Should accept empty"),
init: vec![vec![0, 0x81], vec![1]],
dstack: vec![vec![0, 0x81]],
},
]);
}
#[test]
fn test_logical_ops() {
run_success_test_cases(vec![
TestCase {
code: opcodes::OpEqual::empty().expect("Should accept empty"),
init: vec![vec![], vec![]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpEqual::empty().expect("Should accept empty"),
init: vec![vec![0, 1, 1, 0], vec![0, 1, 1, 0]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpEqual::empty().expect("Should accept empty"),
init: vec![vec![], vec![0]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpEqual::empty().expect("Should accept empty"),
init: vec![vec![0, 1, 1, 0], vec![0, 1, 1, 1]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpBoolAnd::empty().expect("Should accept empty"),
init: vec![vec![], vec![]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpBoolAnd::empty().expect("Should accept empty"),
init: vec![vec![1], vec![]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpBoolAnd::empty().expect("Should accept empty"),
init: vec![vec![], vec![1]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpBoolAnd::empty().expect("Should accept empty"),
init: vec![vec![1], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpBoolAnd::empty().expect("Should accept empty"),
init: vec![vec![1], vec![0x81]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpBoolOr::empty().expect("Should accept empty"),
init: vec![vec![], vec![]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpBoolOr::empty().expect("Should accept empty"),
init: vec![vec![1], vec![]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpBoolOr::empty().expect("Should accept empty"),
init: vec![vec![], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpBoolOr::empty().expect("Should accept empty"),
init: vec![vec![1], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpBoolOr::empty().expect("Should accept empty"),
init: vec![vec![0x81], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpBoolOr::empty().expect("Should accept empty"),
init: vec![vec![0x81], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpNumEqual::empty().expect("Should accept empty"),
init: vec![vec![1], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpNumEqual::empty().expect("Should accept empty"),
init: vec![vec![], vec![1]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpNumEqual::empty().expect("Should accept empty"),
init: vec![vec![0x81], vec![1]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpNumEqual::empty().expect("Should accept empty"),
init: vec![vec![], vec![]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpNumNotEqual::empty().expect("Should accept empty"),
init: vec![vec![1], vec![1]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpNumNotEqual::empty().expect("Should accept empty"),
init: vec![vec![], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpNumNotEqual::empty().expect("Should accept empty"),
init: vec![vec![0x81], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpNumNotEqual::empty().expect("Should accept empty"),
init: vec![vec![], vec![]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpLessThan::empty().expect("Should accept empty"),
init: vec![vec![1], vec![1]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpLessThan::empty().expect("Should accept empty"),
init: vec![vec![], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpLessThan::empty().expect("Should accept empty"),
init: vec![vec![0x81], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpLessThan::empty().expect("Should accept empty"),
init: vec![vec![], vec![]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpLessThan::empty().expect("Should accept empty"),
init: vec![vec![1], vec![]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpLessThan::empty().expect("Should accept empty"),
init: vec![vec![], vec![0x81]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpLessThan::empty().expect("Should accept empty"),
init: vec![vec![1], vec![0x81]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpLessThanOrEqual::empty().expect("Should accept empty"),
init: vec![vec![1], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpLessThanOrEqual::empty().expect("Should accept empty"),
init: vec![vec![], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpLessThanOrEqual::empty().expect("Should accept empty"),
init: vec![vec![0x81], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpLessThanOrEqual::empty().expect("Should accept empty"),
init: vec![vec![], vec![]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpLessThanOrEqual::empty().expect("Should accept empty"),
init: vec![vec![1], vec![]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpLessThanOrEqual::empty().expect("Should accept empty"),
init: vec![vec![], vec![0x81]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpLessThanOrEqual::empty().expect("Should accept empty"),
init: vec![vec![1], vec![0x81]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpGreaterThan::empty().expect("Should accept empty"),
init: vec![vec![1], vec![1]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpGreaterThan::empty().expect("Should accept empty"),
init: vec![vec![], vec![1]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpGreaterThan::empty().expect("Should accept empty"),
init: vec![vec![0x81], vec![1]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpGreaterThan::empty().expect("Should accept empty"),
init: vec![vec![], vec![]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpGreaterThan::empty().expect("Should accept empty"),
init: vec![vec![1], vec![]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpGreaterThan::empty().expect("Should accept empty"),
init: vec![vec![], vec![0x81]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpGreaterThan::empty().expect("Should accept empty"),
init: vec![vec![1], vec![0x81]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpGreaterThanOrEqual::empty().expect("Should accept empty"),
init: vec![vec![1], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpGreaterThanOrEqual::empty().expect("Should accept empty"),
init: vec![vec![], vec![1]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpGreaterThanOrEqual::empty().expect("Should accept empty"),
init: vec![vec![0x81], vec![1]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpGreaterThanOrEqual::empty().expect("Should accept empty"),
init: vec![vec![], vec![]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpGreaterThanOrEqual::empty().expect("Should accept empty"),
init: vec![vec![1], vec![]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpGreaterThanOrEqual::empty().expect("Should accept empty"),
init: vec![vec![], vec![0x81]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpGreaterThanOrEqual::empty().expect("Should accept empty"),
init: vec![vec![1], vec![0x81]],
dstack: vec![vec![1]],
},
]);
}
#[test]
fn test_opdepth() {
run_success_test_cases(vec![
TestCase { code: opcodes::OpDepth::empty().expect("Should accept empty"), init: vec![], dstack: vec![vec![]] },
TestCase {
code: opcodes::OpDepth::empty().expect("Should accept empty"),
init: vec![vec![]],
dstack: vec![vec![], vec![1]],
},
TestCase {
code: opcodes::OpDepth::empty().expect("Should accept empty"),
init: vec![vec![1], vec![2], vec![3]],
dstack: vec![vec![1], vec![2], vec![3], vec![3]],
},
]);
}
#[test]
fn test_opdrop() {
run_success_test_cases(vec![
TestCase { code: opcodes::OpDrop::empty().expect("Should accept empty"), init: vec![vec![]], dstack: vec![] },
TestCase {
code: opcodes::OpDrop::empty().expect("Should accept empty"),
init: vec![vec![], vec![1]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpDrop::empty().expect("Should accept empty"),
init: vec![vec![1], vec![2], vec![3], vec![3]],
dstack: vec![vec![1], vec![2], vec![3]],
},
]);
run_error_test_cases(vec![ErrorTestCase {
code: opcodes::OpDrop::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(1, 0),
}])
}
#[test]
fn test_op2drop() {
run_success_test_cases(vec![
TestCase { code: opcodes::Op2Drop::empty().expect("Should accept empty"), init: vec![vec![], vec![1]], dstack: vec![] },
TestCase {
code: opcodes::Op2Drop::empty().expect("Should accept empty"),
init: vec![vec![1], vec![2], vec![3], vec![3]],
dstack: vec![vec![1], vec![2]],
},
]);
run_error_test_cases(vec![
ErrorTestCase {
code: opcodes::Op2Drop::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(2, 0),
},
ErrorTestCase {
code: opcodes::Op2Drop::empty().expect("Should accept empty"),
init: vec![vec![]],
error: TxScriptError::InvalidStackOperation(2, 1),
},
])
}
#[test]
fn test_opdup() {
run_success_test_cases(vec![
TestCase { code: opcodes::OpDup::empty().expect("Should accept empty"), init: vec![vec![]], dstack: vec![vec![], vec![]] },
TestCase {
code: opcodes::OpDup::empty().expect("Should accept empty"),
init: vec![vec![], vec![1]],
dstack: vec![vec![], vec![1], vec![1]],
},
TestCase {
code: opcodes::OpDup::empty().expect("Should accept empty"),
init: vec![vec![1], vec![2], vec![3], vec![3]],
dstack: vec![vec![1], vec![2], vec![3], vec![3], vec![3]],
},
]);
run_error_test_cases(vec![ErrorTestCase {
code: opcodes::OpDup::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(1, 0),
}])
}
#[test]
fn test_op2dup() {
run_success_test_cases(vec![
TestCase {
code: opcodes::Op2Dup::empty().expect("Should accept empty"),
init: vec![vec![], vec![1]],
dstack: vec![vec![], vec![1], vec![], vec![1]],
},
TestCase {
code: opcodes::Op2Dup::empty().expect("Should accept empty"),
init: vec![vec![1], vec![2], vec![3]],
dstack: vec![vec![1], vec![2], vec![3], vec![2], vec![3]],
},
]);
run_error_test_cases(vec![
ErrorTestCase {
code: opcodes::Op2Dup::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(2, 0),
},
ErrorTestCase {
code: opcodes::Op2Dup::empty().expect("Should accept empty"),
init: vec![vec![]],
error: TxScriptError::InvalidStackOperation(2, 1),
},
]);
}
#[test]
fn test_op3dup() {
run_success_test_cases(vec![
TestCase {
code: opcodes::Op3Dup::empty().expect("Should accept empty"),
init: vec![vec![0x81], vec![], vec![1]],
dstack: vec![vec![0x81], vec![], vec![1], vec![0x81], vec![], vec![1]],
},
TestCase {
code: opcodes::Op3Dup::empty().expect("Should accept empty"),
init: vec![vec![1], vec![2], vec![3]],
dstack: vec![vec![1], vec![2], vec![3], vec![1], vec![2], vec![3]],
},
]);
run_error_test_cases(vec![
ErrorTestCase {
code: opcodes::Op3Dup::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(3, 0),
},
ErrorTestCase {
code: opcodes::Op3Dup::empty().expect("Should accept empty"),
init: vec![vec![]],
error: TxScriptError::InvalidStackOperation(3, 1),
},
ErrorTestCase {
code: opcodes::Op3Dup::empty().expect("Should accept empty"),
init: vec![vec![], vec![]],
error: TxScriptError::InvalidStackOperation(3, 2),
},
]);
}
#[test]
fn test_opnip() {
run_success_test_cases(vec![
TestCase {
code: opcodes::OpNip::empty().expect("Should accept empty"),
init: vec![vec![], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpNip::empty().expect("Should accept empty"),
init: vec![vec![1], vec![]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpNip::empty().expect("Should accept empty"),
init: vec![vec![2], vec![], vec![1]],
dstack: vec![vec![2], vec![1]],
},
]);
run_error_test_cases(vec![
ErrorTestCase {
code: opcodes::OpNip::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(2, 0),
},
ErrorTestCase {
code: opcodes::OpNip::empty().expect("Should accept empty"),
init: vec![vec![]],
error: TxScriptError::InvalidStackOperation(2, 1),
},
]);
}
#[test]
fn test_opover() {
run_success_test_cases(vec![
TestCase {
code: opcodes::OpOver::empty().expect("Should accept empty"),
init: vec![vec![], vec![1]],
dstack: vec![vec![], vec![1], vec![]],
},
TestCase {
code: opcodes::OpOver::empty().expect("Should accept empty"),
init: vec![vec![1], vec![]],
dstack: vec![vec![1], vec![], vec![1]],
},
TestCase {
code: opcodes::OpOver::empty().expect("Should accept empty"),
init: vec![vec![2], vec![], vec![1]],
dstack: vec![vec![2], vec![], vec![1], vec![]],
},
]);
run_error_test_cases(vec![
ErrorTestCase {
code: opcodes::OpOver::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(2, 0),
},
ErrorTestCase {
code: opcodes::OpOver::empty().expect("Should accept empty"),
init: vec![vec![]],
error: TxScriptError::InvalidStackOperation(2, 1),
},
]);
}
#[test]
fn test_op2over() {
run_success_test_cases(vec![
TestCase {
code: opcodes::Op2Over::empty().expect("Should accept empty"),
init: vec![vec![0x81], vec![2], vec![], vec![1]],
dstack: vec![vec![0x81], vec![2], vec![], vec![1], vec![0x81], vec![2]],
},
TestCase {
code: opcodes::Op2Over::empty().expect("Should accept empty"),
init: vec![vec![], vec![0x81], vec![2], vec![], vec![1]],
dstack: vec![vec![], vec![0x81], vec![2], vec![], vec![1], vec![0x81], vec![2]],
},
]);
run_error_test_cases(vec![
ErrorTestCase {
code: opcodes::Op2Over::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(4, 0),
},
ErrorTestCase {
code: opcodes::Op2Over::empty().expect("Should accept empty"),
init: vec![vec![]],
error: TxScriptError::InvalidStackOperation(4, 1),
},
ErrorTestCase {
code: opcodes::Op2Over::empty().expect("Should accept empty"),
init: vec![vec![], vec![]],
error: TxScriptError::InvalidStackOperation(4, 2),
},
ErrorTestCase {
code: opcodes::Op2Over::empty().expect("Should accept empty"),
init: vec![vec![], vec![], vec![]],
error: TxScriptError::InvalidStackOperation(4, 3),
},
]);
}
#[test]
fn test_oppick() {
run_success_test_cases(vec![
TestCase {
code: opcodes::OpPick::empty().expect("Should accept empty"),
init: vec![vec![], vec![]],
dstack: vec![vec![], vec![]],
},
TestCase {
code: opcodes::OpPick::empty().expect("Should accept empty"),
init: vec![vec![2], vec![], vec![1]],
dstack: vec![vec![2], vec![], vec![2]],
},
TestCase {
code: opcodes::OpPick::empty().expect("Should accept empty"),
init: vec![vec![5], vec![4], vec![3], vec![], vec![2]],
dstack: vec![vec![5], vec![4], vec![3], vec![], vec![4]],
},
]);
run_error_test_cases(vec![
ErrorTestCase {
code: opcodes::OpPick::empty().expect("Should accept empty"),
init: vec![vec![5], vec![4], vec![3], vec![], vec![4]],
error: TxScriptError::InvalidState("pick at an invalid location".to_string()),
},
ErrorTestCase {
code: opcodes::OpPick::empty().expect("Should accept empty"),
init: vec![vec![5], vec![4], vec![3], vec![], vec![0x81]],
error: TxScriptError::InvalidState("pick at an invalid location".to_string()),
},
])
}
#[test]
fn test_oproll() {
run_success_test_cases(vec![
TestCase {
code: opcodes::OpRoll::empty().expect("Should accept empty"),
init: vec![vec![], vec![]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpRoll::empty().expect("Should accept empty"),
init: vec![vec![2], vec![], vec![1]],
dstack: vec![vec![], vec![2]],
},
TestCase {
code: opcodes::OpRoll::empty().expect("Should accept empty"),
init: vec![vec![5], vec![4], vec![3], vec![], vec![2]],
dstack: vec![vec![5], vec![3], vec![], vec![4]],
},
]);
run_error_test_cases(vec![
ErrorTestCase {
code: opcodes::OpRoll::empty().expect("Should accept empty"),
init: vec![vec![5], vec![4], vec![3], vec![], vec![4]],
error: TxScriptError::InvalidState("roll at an invalid location".to_string()),
},
ErrorTestCase {
code: opcodes::OpRoll::empty().expect("Should accept empty"),
init: vec![vec![5], vec![4], vec![3], vec![], vec![0x81]],
error: TxScriptError::InvalidState("roll at an invalid location".to_string()),
},
])
}
#[test]
fn test_oprot() {
run_success_test_cases(vec![
TestCase {
code: opcodes::OpRot::empty().expect("Should accept empty"),
init: vec![vec![1], vec![2], vec![3]],
dstack: vec![vec![2], vec![3], vec![1]],
},
TestCase {
code: opcodes::OpRot::empty().expect("Should accept empty"),
init: vec![vec![], vec![1], vec![2], vec![3]],
dstack: vec![vec![], vec![2], vec![3], vec![1]],
},
]);
run_error_test_cases(vec![
ErrorTestCase {
code: opcodes::OpRot::empty().expect("Should accept empty"),
init: vec![vec![2], vec![3]],
error: TxScriptError::InvalidStackOperation(3, 2),
},
ErrorTestCase {
code: opcodes::OpRot::empty().expect("Should accept empty"),
init: vec![vec![3]],
error: TxScriptError::InvalidStackOperation(3, 1),
},
ErrorTestCase {
code: opcodes::OpRot::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(3, 0),
},
]);
}
#[test]
fn test_op2rot() {
run_success_test_cases(vec![
TestCase {
code: opcodes::Op2Rot::empty().expect("Should accept empty"),
init: vec![vec![1], vec![2], vec![3], vec![4], vec![5], vec![6]],
dstack: vec![vec![3], vec![4], vec![5], vec![6], vec![1], vec![2]],
},
TestCase {
code: opcodes::Op2Rot::empty().expect("Should accept empty"),
init: vec![vec![], vec![1], vec![2], vec![3], vec![4], vec![5], vec![6]],
dstack: vec![vec![], vec![3], vec![4], vec![5], vec![6], vec![1], vec![2]],
},
]);
run_error_test_cases(vec![
ErrorTestCase {
code: opcodes::Op2Rot::empty().expect("Should accept empty"),
init: vec![vec![1], vec![2], vec![3], vec![4], vec![5]],
error: TxScriptError::InvalidStackOperation(6, 5),
},
ErrorTestCase {
code: opcodes::Op2Rot::empty().expect("Should accept empty"),
init: vec![vec![1], vec![2], vec![3], vec![4]],
error: TxScriptError::InvalidStackOperation(6, 4),
},
ErrorTestCase {
code: opcodes::Op2Rot::empty().expect("Should accept empty"),
init: vec![vec![1], vec![2], vec![3]],
error: TxScriptError::InvalidStackOperation(6, 3),
},
ErrorTestCase {
code: opcodes::Op2Rot::empty().expect("Should accept empty"),
init: vec![vec![1], vec![2]],
error: TxScriptError::InvalidStackOperation(6, 2),
},
ErrorTestCase {
code: opcodes::Op2Rot::empty().expect("Should accept empty"),
init: vec![vec![1]],
error: TxScriptError::InvalidStackOperation(6, 1),
},
ErrorTestCase {
code: opcodes::Op2Rot::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(6, 0),
},
]);
}
#[test]
fn test_opswap() {
run_success_test_cases(vec![
TestCase {
code: opcodes::OpSwap::empty().expect("Should accept empty"),
init: vec![vec![1], vec![2]],
dstack: vec![vec![2], vec![1]],
},
TestCase {
code: opcodes::OpSwap::empty().expect("Should accept empty"),
init: vec![vec![], vec![1], vec![5]],
dstack: vec![vec![], vec![5], vec![1]],
},
]);
run_error_test_cases(vec![
ErrorTestCase {
code: opcodes::OpSwap::empty().expect("Should accept empty"),
init: vec![vec![1]],
error: TxScriptError::InvalidStackOperation(2, 1),
},
ErrorTestCase {
code: opcodes::OpSwap::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(2, 0),
},
]);
}
#[test]
fn test_op2swap() {
run_success_test_cases(vec![
TestCase {
code: opcodes::Op2Swap::empty().expect("Should accept empty"),
init: vec![vec![1], vec![2], vec![3], vec![4]],
dstack: vec![vec![3], vec![4], vec![1], vec![2]],
},
TestCase {
code: opcodes::Op2Swap::empty().expect("Should accept empty"),
init: vec![vec![], vec![1], vec![2], vec![3], vec![4]],
dstack: vec![vec![], vec![3], vec![4], vec![1], vec![2]],
},
]);
run_error_test_cases(vec![
ErrorTestCase {
code: opcodes::Op2Swap::empty().expect("Should accept empty"),
init: vec![vec![], vec![2], vec![1]],
error: TxScriptError::InvalidStackOperation(4, 3),
},
ErrorTestCase {
code: opcodes::Op2Swap::empty().expect("Should accept empty"),
init: vec![vec![], vec![1]],
error: TxScriptError::InvalidStackOperation(4, 2),
},
ErrorTestCase {
code: opcodes::Op2Swap::empty().expect("Should accept empty"),
init: vec![vec![1]],
error: TxScriptError::InvalidStackOperation(4, 1),
},
ErrorTestCase {
code: opcodes::Op2Swap::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(4, 0),
},
]);
}
#[test]
fn test_optuck() {
run_success_test_cases(vec![
TestCase {
code: opcodes::OpTuck::empty().expect("Should accept empty"),
init: vec![vec![1], vec![2]],
dstack: vec![vec![2], vec![1], vec![2]],
},
TestCase {
code: opcodes::OpTuck::empty().expect("Should accept empty"),
init: vec![vec![3], vec![9], vec![2]],
dstack: vec![vec![3], vec![2], vec![9], vec![2]],
},
]);
run_error_test_cases(vec![
ErrorTestCase {
code: opcodes::OpTuck::empty().expect("Should accept empty"),
init: vec![vec![3]],
error: TxScriptError::InvalidStackOperation(2, 1),
},
ErrorTestCase {
code: opcodes::OpTuck::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(2, 0),
},
]);
}
#[test]
fn test_opequalverify() {
run_success_test_cases(vec![
TestCase {
code: opcodes::OpEqualVerify::empty().expect("Should accept empty"),
init: vec![vec![], vec![]],
dstack: vec![],
},
TestCase {
code: opcodes::OpEqualVerify::empty().expect("Should accept empty"),
init: vec![vec![], vec![1, 0, 1], vec![1, 0, 1]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpNumEqualVerify::empty().expect("Should accept empty"),
init: vec![vec![], vec![]],
dstack: vec![],
},
TestCase {
code: opcodes::OpNumEqualVerify::empty().expect("Should accept empty"),
init: vec![vec![], vec![0, 0, 1], vec![0, 0, 1]],
dstack: vec![vec![]],
},
]);
run_error_test_cases(vec![
ErrorTestCase {
code: opcodes::OpEqualVerify::empty().expect("Should accept empty"),
init: vec![vec![], vec![2, 0, 1], vec![1, 0, 1]],
error: TxScriptError::VerifyError,
},
ErrorTestCase {
code: opcodes::OpEqualVerify::empty().expect("Should accept empty"),
init: vec![vec![1], vec![]],
error: TxScriptError::VerifyError,
},
ErrorTestCase {
code: opcodes::OpNumEqualVerify::empty().expect("Should accept empty"),
init: vec![vec![], vec![2, 0, 1], vec![1, 0, 1]],
error: TxScriptError::VerifyError,
},
ErrorTestCase {
code: opcodes::OpNumEqualVerify::empty().expect("Should accept empty"),
init: vec![vec![1], vec![]],
error: TxScriptError::VerifyError,
},
]);
}
#[test]
fn test_opsize() {
run_success_test_cases(vec![
TestCase {
code: opcodes::OpSize::empty().expect("Should accept empty"),
init: vec![vec![]],
dstack: vec![vec![], vec![]],
},
TestCase {
code: opcodes::OpSize::empty().expect("Should accept empty"),
init: vec![vec![5]],
dstack: vec![vec![5], vec![1]],
},
TestCase {
code: opcodes::OpSize::empty().expect("Should accept empty"),
init: vec![vec![0x80, 1]],
dstack: vec![vec![0x80, 1], vec![2]],
},
]);
run_error_test_cases(vec![ErrorTestCase {
code: opcodes::OpSize::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(1, 0),
}]);
}
#[test]
fn test_opwithin() {
run_success_test_cases(vec![
TestCase {
code: opcodes::OpWithin::empty().expect("Should accept empty"),
init: vec![vec![], vec![], vec![1]],
dstack: vec![vec![1]],
},
TestCase {
code: opcodes::OpWithin::empty().expect("Should accept empty"),
init: vec![vec![], vec![], vec![]],
dstack: vec![vec![]],
},
TestCase {
code: opcodes::OpWithin::empty().expect("Should accept empty"),
init: vec![vec![0x81], vec![0x91], vec![1]],
dstack: vec![vec![1]],
},
]);
run_error_test_cases(vec![
ErrorTestCase {
code: opcodes::OpWithin::empty().expect("Should accept empty"),
init: vec![vec![], vec![]],
error: TxScriptError::InvalidStackOperation(3, 2),
},
ErrorTestCase {
code: opcodes::OpWithin::empty().expect("Should accept empty"),
init: vec![vec![]],
error: TxScriptError::InvalidStackOperation(3, 1),
},
ErrorTestCase {
code: opcodes::OpWithin::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(3, 0),
},
]);
}
#[test]
fn test_opsha256() {
run_success_test_cases(vec![
TestCase {
code: opcodes::OpSHA256::empty().expect("Should accept empty"),
init: vec![vec![]],
dstack: vec![b"\xe3\xb0\xc4\x42\x98\xfc\x1c\x14\x9a\xfb\xf4\xc8\x99\x6f\xb9\x24\x27\xae\x41\xe4\x64\x9b\x93\x4c\xa4\x95\x99\x1b\x78\x52\xb8\x55".to_vec()],
},
TestCase {
code: opcodes::OpSHA256::empty().expect("Should accept empty"),
init: vec![b"abc".to_vec()],
dstack: vec![b"\xba\x78\x16\xbf\x8f\x01\xcf\xea\x41\x41\x40\xde\x5d\xae\x22\x23\xb0\x03\x61\xa3\x96\x17\x7a\x9c\xb4\x10\xff\x61\xf2\x00\x15\xad".to_vec()],
},
TestCase {
code: opcodes::OpSHA256::empty().expect("Should accept empty"),
init: vec![b"\xde\x18\x89\x41\xa3\x37\x5d\x3a\x8a\x06\x1e\x67\x57\x6e\x92\x6d".to_vec()],
dstack: vec![b"\x06\x7c\x53\x12\x69\x73\x5c\xa7\xf5\x41\xfd\xac\xa8\xf0\xdc\x76\x30\x5d\x3c\xad\xa1\x40\xf8\x93\x72\xa4\x10\xfe\x5e\xff\x6e\x4d".to_vec()],
},
]);
run_error_test_cases(vec![ErrorTestCase {
code: opcodes::OpSHA256::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(1, 0),
}]);
}
#[test]
fn test_opblake2b() {
run_success_test_cases(vec![
TestCase {
code: opcodes::OpBlake2b::empty().expect("Should accept empty"),
init: vec![b"".to_vec()],
dstack: vec![b"\x0e\x57\x51\xc0\x26\xe5\x43\xb2\xe8\xab\x2e\xb0\x60\x99\xda\xa1\xd1\xe5\xdf\x47\x77\x8f\x77\x87\xfa\xab\x45\xcd\xf1\x2f\xe3\xa8".to_vec()],
},
TestCase {
code: opcodes::OpBlake2b::empty().expect("Should accept empty"),
init: vec![b"abc".to_vec()],
dstack: vec![b"\xbd\xdd\x81\x3c\x63\x42\x39\x72\x31\x71\xef\x3f\xee\x98\x57\x9b\x94\x96\x4e\x3b\xb1\xcb\x3e\x42\x72\x62\xc8\xc0\x68\xd5\x23\x19".to_vec()],
},
]);
run_error_test_cases(vec![ErrorTestCase {
code: opcodes::OpBlake2b::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(1, 0),
}]);
}
#[test]
fn test_opnop() {
run_success_test_cases(vec![TestCase {
code: opcodes::OpNop::empty().expect("Should accept empty"),
init: vec![vec![], vec![1], vec![2]],
dstack: vec![vec![], vec![1], vec![2]],
}]);
}
#[derive(Clone)]
struct VerifiableTransactionMock(Transaction);
impl VerifiableTransaction for VerifiableTransactionMock {
fn tx(&self) -> &Transaction {
&self.0
}
fn populated_input(&self, _index: usize) -> (&TransactionInput, &UtxoEntry) {
unimplemented!()
}
fn utxo(&self, _index: usize) -> Option<&UtxoEntry> {
unimplemented!()
}
}
fn make_mock_transaction(lock_time: u64) -> (VerifiableTransactionMock, TransactionInput, UtxoEntry) {
let dummy_prev_out = TransactionOutpoint::new(kaspa_hashes::Hash::from_u64_word(1), 1);
let dummy_sig_script = vec![0u8; 65];
let dummy_tx_input = TransactionInput::new(dummy_prev_out, dummy_sig_script, 10, 1);
let addr_hash = vec![1u8; 32];
let addr = Address::new(Prefix::Testnet, Version::PubKey, &addr_hash);
let dummy_script_public_key = pay_to_address_script(&addr);
let dummy_tx_out = TransactionOutput::new(SOMPI_PER_KASPA, dummy_script_public_key);
let tx = VerifiableTransactionMock(Transaction::new(
TX_VERSION + 1,
vec![dummy_tx_input.clone()],
vec![dummy_tx_out.clone()],
lock_time,
SUBNETWORK_ID_NATIVE,
0,
vec![],
));
let utxo_entry = UtxoEntry::new(0, ScriptPublicKey::default(), 0, false, None);
(tx, dummy_tx_input, utxo_entry)
}
#[test]
fn test_opchecklocktimeverify() {
let (base_tx, input, utxo_entry) = make_mock_transaction(1);
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&sig_cache).with_reused(&reused_values);
let code = opcodes::OpCheckLockTimeVerify::empty().expect("Should accept empty");
for (tx_lock_time, lock_time, should_fail) in [
(1u64, vec![], false), (0x800000, vec![0x7f, 0, 0], false), (0x800000, vec![0x7f, 0, 0, 0, 0, 0, 0, 0, 0], true), (LOCK_TIME_THRESHOLD * 2, vec![0x7f, 0, 0, 0], true), ] {
let mut tx = base_tx.clone();
tx.0.lock_time = tx_lock_time;
let mut vm = TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, ctx, Default::default());
vm.dstack = vec![lock_time.clone()].into();
match code.execute(&mut vm) {
Ok(()) => assert!(
!should_fail,
"Opcode {} must fail (tx_lock_time: {}, lock_time: {:?})",
code.value(),
tx_lock_time,
lock_time
),
Err(e) => assert!(
should_fail,
"Opcode {} should not fail. Got {} (tx_lock_time: {}, lock_time: {:?})",
code.value(),
e,
tx_lock_time,
lock_time
),
}
}
}
#[test]
fn test_opchecksequencerify() {
let (tx, base_input, utxo_entry) = make_mock_transaction(1);
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&sig_cache).with_reused(&reused_values);
let code = opcodes::OpCheckSequenceVerify::empty().expect("Should accept empty");
for (tx_sequence, sequence, should_fail) in [
(1u64, vec![], false), (0x800000, vec![0x7f, 0, 0], false), (0x800000, vec![0x7f, 0, 0, 0, 0, 0, 0, 0, 0], true), (1 << 63, vec![0x7f, 0, 0], true), ((1 << 63) | 0xffff, vec![0x7f, 0, 0], true), ] {
let mut input = base_input.clone();
input.sequence = tx_sequence;
let mut vm = TxScriptEngine::from_transaction_input(&tx, &input, 0, &utxo_entry, ctx, Default::default());
vm.dstack = vec![sequence.clone()].into();
match code.execute(&mut vm) {
Ok(()) => {
assert!(!should_fail, "Opcode {} must fail (tx_sequence: {}, sequence: {:?})", code.value(), tx_sequence, sequence)
}
Err(e) => assert!(
should_fail,
"Opcode {} should not fail. Got {} (tx_sequence: {}, sequence: {:?})",
code.value(),
e,
tx_sequence,
sequence
),
}
}
}
#[test]
fn test_opreturn() {
run_error_test_cases(vec![ErrorTestCase {
code: opcodes::OpReturn::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::EarlyReturn,
}]);
}
#[test]
fn test_opverify() {
run_success_test_cases(vec![
TestCase { code: opcodes::OpVerify::empty().expect("Should accept empty"), init: vec![vec![1]], dstack: vec![] },
TestCase { code: opcodes::OpVerify::empty().expect("Should accept empty"), init: vec![vec![0x81]], dstack: vec![] },
TestCase { code: opcodes::OpVerify::empty().expect("Should accept empty"), init: vec![vec![0x80, 0]], dstack: vec![] },
TestCase {
code: opcodes::OpVerify::empty().expect("Should accept empty"),
init: vec![vec![1, 0, 0, 0, 0]],
dstack: vec![],
},
]);
run_error_test_cases(vec![
ErrorTestCase {
code: opcodes::OpVerify::empty().expect("Should accept empty"),
init: vec![vec![0, 0, 0, 0x80]],
error: TxScriptError::VerifyError,
},
ErrorTestCase {
code: opcodes::OpVerify::empty().expect("Should accept empty"),
init: vec![vec![0, 0, 0, 0]],
error: TxScriptError::VerifyError,
},
ErrorTestCase {
code: opcodes::OpVerify::empty().expect("Should accept empty"),
init: vec![vec![0x80]],
error: TxScriptError::VerifyError,
},
ErrorTestCase {
code: opcodes::OpVerify::empty().expect("Should accept empty"),
init: vec![vec![0]],
error: TxScriptError::VerifyError,
},
ErrorTestCase {
code: opcodes::OpVerify::empty().expect("Should accept empty"),
init: vec![vec![]],
error: TxScriptError::VerifyError,
},
ErrorTestCase {
code: opcodes::OpVerify::empty().expect("Should accept empty"),
init: vec![],
error: TxScriptError::InvalidStackOperation(1, 0),
},
])
}
#[test]
fn test_opifdup() {
run_success_test_cases(vec![
TestCase {
code: opcodes::OpIfDup::empty().expect("Should accept empty"),
init: vec![vec![1]],
dstack: vec![vec![1], vec![1]],
},
TestCase {
code: opcodes::OpIfDup::empty().expect("Should accept empty"),
init: vec![vec![0x80, 0]],
dstack: vec![vec![0x80, 0], vec![0x80, 0]],
},
TestCase { code: opcodes::OpIfDup::empty().expect("Should accept empty"), init: vec![vec![]], dstack: vec![vec![]] },
TestCase {
code: opcodes::OpIfDup::empty().expect("Should accept empty"),
init: vec![vec![0x80]],
dstack: vec![vec![0x80]],
},
TestCase {
code: opcodes::OpIfDup::empty().expect("Should accept empty"),
init: vec![vec![0, 0x80]],
dstack: vec![vec![0, 0x80]],
},
TestCase { code: opcodes::OpIfDup::empty().expect("Should accept empty"), init: vec![vec![]], dstack: vec![vec![]] },
])
}
mod kip10 {
use super::*;
use crate::{
SpkEncoding,
data_stack::OpcodeData,
opcodes::{codes::*, push_number},
pay_to_script_hash_script,
script_builder::ScriptBuilder,
};
use kaspa_consensus_core::tx::MutableTransaction;
#[derive(Clone, Debug)]
struct Kip10Mock {
spk: ScriptPublicKey,
amount: u64,
}
fn create_mock_spk(value: u8) -> ScriptPublicKey {
let pub_key = vec![value; 32];
let addr = Address::new(Prefix::Testnet, Version::PubKey, &pub_key);
pay_to_address_script(&addr)
}
fn kip_10_tx_mock(inputs: Vec<Kip10Mock>, outputs: Vec<Kip10Mock>) -> (Transaction, Vec<UtxoEntry>) {
let dummy_prev_out = TransactionOutpoint::new(kaspa_hashes::Hash::from_u64_word(1), 1);
let dummy_sig_script = vec![0u8; 65];
let (utxos, tx_inputs) = inputs
.into_iter()
.map(|Kip10Mock { spk, amount }| {
(
UtxoEntry::new(amount, spk, 0, false, None),
TransactionInput::new(dummy_prev_out, dummy_sig_script.clone(), 10, 0),
)
})
.unzip();
let tx_out = outputs.into_iter().map(|Kip10Mock { spk, amount }| TransactionOutput::new(amount, spk));
let tx = Transaction::new(TX_VERSION + 1, tx_inputs, tx_out.collect(), 0, SUBNETWORK_ID_NATIVE, 0, vec![]);
(tx, utxos)
}
#[derive(Debug)]
struct TestGroup {
name: &'static str,
test_cases: Vec<TestCase>,
}
#[derive(Debug)]
enum Operation {
InputSpk,
OutputSpk,
InputAmount,
OutputAmount,
}
#[derive(Debug)]
enum TestCase {
Successful { operation: Operation, index: i64, expected_result: ExpectedResult },
Incorrect { operation: Operation, index: Option<i64>, expected_error: TxScriptError },
}
#[derive(Debug)]
struct ExpectedResult {
expected_spk: Option<Vec<u8>>,
expected_amount: Option<Vec<u8>>,
}
fn execute_test_group(group: &TestGroup) {
let input_spk1 = create_mock_spk(1);
let input_spk2 = create_mock_spk(2);
let output_spk1 = create_mock_spk(3);
let output_spk2 = create_mock_spk(4);
let inputs = vec![
Kip10Mock { spk: input_spk1.clone(), amount: 1111 },
Kip10Mock { spk: input_spk2.clone(), amount: 2222 },
Kip10Mock { spk: create_mock_spk(5), amount: 5555 },
];
let outputs =
vec![Kip10Mock { spk: output_spk1.clone(), amount: 3333 }, Kip10Mock { spk: output_spk2.clone(), amount: 4444 }];
let (tx, utxo_entries) = kip_10_tx_mock(inputs, outputs);
let tx = PopulatedTransaction::new(&tx, utxo_entries);
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&sig_cache).with_reused(&reused_values);
for current_idx in 0..tx.inputs().len() {
let mut vm = TxScriptEngine::from_transaction_input(
&tx,
&tx.inputs()[current_idx],
current_idx,
tx.utxo(current_idx).unwrap(),
ctx,
Default::default(),
);
let op_input_idx = opcodes::OpTxInputIndex::empty().expect("Should accept empty");
let mut expected = vm.dstack.clone();
expected.push_item(current_idx as i64).unwrap();
op_input_idx.execute(&mut vm).unwrap();
assert_eq!(vm.dstack, expected);
vm.dstack.clear();
let op_input_spk = opcodes::OpTxInputSpk::empty().expect("Should accept empty");
let op_output_spk = opcodes::OpTxOutputSpk::empty().expect("Should accept empty");
let op_input_amount = opcodes::OpTxInputAmount::empty().expect("Should accept empty");
let op_output_amount = opcodes::OpTxOutputAmount::empty().expect("Should accept empty");
for test_case in &group.test_cases {
match test_case {
TestCase::Successful { operation, index, expected_result } => {
push_number(*index, &mut vm).unwrap();
let result = match operation {
Operation::InputSpk => op_input_spk.execute(&mut vm),
Operation::OutputSpk => op_output_spk.execute(&mut vm),
Operation::InputAmount => op_input_amount.execute(&mut vm),
Operation::OutputAmount => op_output_amount.execute(&mut vm),
};
assert!(result.is_ok());
if let Some(ref expected_spk) = expected_result.expected_spk {
assert_eq!(vm.dstack.inner(), vec![StackEntry::from_vec(expected_spk.clone())]);
}
if let Some(ref expected_amount) = expected_result.expected_amount {
assert_eq!(vm.dstack.inner(), vec![StackEntry::from_vec(expected_amount.clone())]);
}
vm.dstack.clear();
}
TestCase::Incorrect { operation, index, expected_error } => {
if let Some(idx) = index {
push_number(*idx, &mut vm).unwrap();
}
let result = match operation {
Operation::InputSpk => op_input_spk.execute(&mut vm),
Operation::OutputSpk => op_output_spk.execute(&mut vm),
Operation::InputAmount => op_input_amount.execute(&mut vm),
Operation::OutputAmount => op_output_amount.execute(&mut vm),
};
assert_eq!(result, Err(expected_error.clone()));
vm.dstack.clear();
}
}
}
}
}
#[test]
fn test_unary_introspection_ops() {
let test_groups = vec![
TestGroup {
name: "Valid input indices",
test_cases: vec![
TestCase::Successful {
operation: Operation::InputSpk,
index: 0,
expected_result: ExpectedResult {
expected_spk: Some(create_mock_spk(1).to_bytes()),
expected_amount: None,
},
},
TestCase::Successful {
operation: Operation::InputSpk,
index: 1,
expected_result: ExpectedResult {
expected_spk: Some(create_mock_spk(2).to_bytes()),
expected_amount: None,
},
},
TestCase::Successful {
operation: Operation::InputAmount,
index: 0,
expected_result: ExpectedResult {
expected_spk: None,
expected_amount: Some(OpcodeData::<i64>::serialize(&1111).unwrap()),
},
},
TestCase::Successful {
operation: Operation::InputAmount,
index: 1,
expected_result: ExpectedResult {
expected_spk: None,
expected_amount: Some(OpcodeData::<i64>::serialize(&2222).unwrap()),
},
},
],
},
TestGroup {
name: "Valid output indices",
test_cases: vec![
TestCase::Successful {
operation: Operation::OutputSpk,
index: 0,
expected_result: ExpectedResult {
expected_spk: Some(create_mock_spk(3).to_bytes()),
expected_amount: None,
},
},
TestCase::Successful {
operation: Operation::OutputSpk,
index: 1,
expected_result: ExpectedResult {
expected_spk: Some(create_mock_spk(4).to_bytes()),
expected_amount: None,
},
},
TestCase::Successful {
operation: Operation::OutputAmount,
index: 0,
expected_result: ExpectedResult {
expected_spk: None,
expected_amount: Some(OpcodeData::<i64>::serialize(&3333).unwrap()),
},
},
TestCase::Successful {
operation: Operation::OutputAmount,
index: 1,
expected_result: ExpectedResult {
expected_spk: None,
expected_amount: Some(OpcodeData::<i64>::serialize(&4444).unwrap()),
},
},
],
},
TestGroup {
name: "Error cases",
test_cases: vec![
TestCase::Incorrect {
operation: Operation::InputAmount,
index: None,
expected_error: TxScriptError::InvalidStackOperation(1, 0),
},
TestCase::Incorrect {
operation: Operation::InputAmount,
index: Some(-1),
expected_error: TxScriptError::InvalidInputIndex(-1, 3),
},
TestCase::Incorrect {
operation: Operation::InputAmount,
index: Some(3),
expected_error: TxScriptError::InvalidInputIndex(3, 3),
},
TestCase::Incorrect {
operation: Operation::OutputAmount,
index: None,
expected_error: TxScriptError::InvalidStackOperation(1, 0),
},
TestCase::Incorrect {
operation: Operation::OutputAmount,
index: Some(-1),
expected_error: TxScriptError::InvalidOutputIndex(-1, 2),
},
TestCase::Incorrect {
operation: Operation::OutputAmount,
index: Some(2),
expected_error: TxScriptError::InvalidOutputIndex(2, 2),
},
TestCase::Incorrect {
operation: Operation::OutputSpk,
index: Some(2),
expected_error: TxScriptError::InvalidOutputIndex(2, 2),
},
],
},
];
for group in test_groups {
println!("Running test group: {}", group.name);
execute_test_group(&group);
}
}
fn create_mock_tx(input_count: usize, output_count: usize) -> (Transaction, Vec<UtxoEntry>) {
let dummy_prev_out = TransactionOutpoint::new(kaspa_hashes::Hash::from_u64_word(1), 1);
let dummy_sig_script = vec![0u8; 65];
let inputs: Vec<Kip10Mock> =
(0..input_count).map(|i| Kip10Mock { spk: create_mock_spk(i as u8), amount: 1000 + i as u64 }).collect();
let outputs: Vec<Kip10Mock> =
(0..output_count).map(|i| Kip10Mock { spk: create_mock_spk((100 + i) as u8), amount: 2000 + i as u64 }).collect();
let (utxos, tx_inputs): (Vec<_>, Vec<_>) = inputs
.into_iter()
.map(|Kip10Mock { spk, amount }| {
(
UtxoEntry::new(amount, spk, 0, false, None),
TransactionInput::new(dummy_prev_out, dummy_sig_script.clone(), 10, 0),
)
})
.unzip();
let tx_outputs: Vec<_> =
outputs.into_iter().map(|Kip10Mock { spk, amount }| TransactionOutput::new(amount, spk)).collect();
let tx = Transaction::new(TX_VERSION + 1, tx_inputs, tx_outputs, 0, SUBNETWORK_ID_NATIVE, 0, vec![]);
(tx, utxos)
}
#[test]
fn test_op_input_output_count() {
let test_cases = vec![
(1, 0), (1, 1), (1, 2), (2, 1), (3, 2), (5, 3), (2, 4), ];
for (input_count, output_count) in test_cases {
let (tx, utxo_entries) = create_mock_tx(input_count, output_count);
let tx = PopulatedTransaction::new(&tx, utxo_entries);
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&sig_cache).with_reused(&reused_values);
let mut vm = TxScriptEngine::from_transaction_input(
&tx,
&tx.inputs()[0], 0,
tx.utxo(0).unwrap(),
ctx,
Default::default(),
);
let op_input_count = opcodes::OpTxInputCount::empty().expect("Should accept empty");
let op_output_count = opcodes::OpTxOutputCount::empty().expect("Should accept empty");
op_input_count.execute(&mut vm).unwrap();
assert_eq!(
vm.dstack.inner(),
vec![<StackEntry as OpcodeData<i64>>::serialize(&(input_count as i64)).unwrap()],
"Input count mismatch for {} inputs",
input_count
);
vm.dstack.clear();
op_output_count.execute(&mut vm).unwrap();
assert_eq!(
vm.dstack.inner(),
vec![<StackEntry as OpcodeData<i64>>::serialize(&(output_count as i64)).unwrap()],
"Output count mismatch for {} outputs",
output_count
);
vm.dstack.clear();
}
}
#[test]
fn test_output_amount() {
let redeem_script = ScriptBuilder::new()
.add_op(Op0)
.unwrap()
.add_op(OpTxOutputAmount)
.unwrap()
.add_i64(100)
.unwrap()
.add_op(OpEqual)
.unwrap()
.drain();
let spk = pay_to_script_hash_script(&redeem_script);
let input_mock = Kip10Mock { spk: spk.clone(), amount: 200 };
let output_mock = Kip10Mock { spk: create_mock_spk(1), amount: 100 };
let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock.clone()], vec![output_mock]);
let mut tx = MutableTransaction::with_entries(tx, utxo_entries);
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain();
let tx = tx.as_verifiable();
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&sig_cache).with_reused(&reused_values);
{
let mut vm =
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), ctx, Default::default());
assert_eq!(vm.execute(), Ok(()));
}
{
let output_mock = Kip10Mock {
spk: create_mock_spk(1),
amount: 99, };
let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock.clone()], vec![output_mock]);
let mut tx = MutableTransaction::with_entries(tx, utxo_entries);
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain();
let tx = tx.as_verifiable();
let mut vm =
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), ctx, Default::default());
assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse));
}
}
#[test]
fn test_input_amount() {
let redeem_script = ScriptBuilder::new()
.add_op(Op0)
.unwrap()
.add_op(OpTxInputAmount)
.unwrap()
.add_i64(200)
.unwrap()
.add_op(OpEqual)
.unwrap()
.drain();
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&sig_cache).with_reused(&reused_values);
let spk = pay_to_script_hash_script(&redeem_script);
{
let input_mock = Kip10Mock { spk: spk.clone(), amount: 200 };
let output_mock = Kip10Mock { spk: create_mock_spk(1), amount: 100 };
let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock], vec![output_mock]);
let mut tx = MutableTransaction::with_entries(tx, utxo_entries);
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain();
let tx = tx.as_verifiable();
let mut vm =
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), ctx, Default::default());
assert_eq!(vm.execute(), Ok(()));
}
{
let input_mock = Kip10Mock {
spk: spk.clone(),
amount: 199, };
let output_mock = Kip10Mock { spk: create_mock_spk(1), amount: 100 };
let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock], vec![output_mock]);
let mut tx = MutableTransaction::with_entries(tx, utxo_entries);
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain();
let tx = tx.as_verifiable();
let mut vm =
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), ctx, Default::default());
assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse));
}
}
#[test]
fn test_input_spk_basic() {
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&sig_cache).with_reused(&reused_values);
let redeem_script = ScriptBuilder::new().add_ops(&[Op0, OpTxInputSpk, OpNop]).unwrap().drain();
let spk = pay_to_script_hash_script(&redeem_script);
let (tx, utxo_entries) = kip_10_tx_mock(vec![Kip10Mock { spk, amount: 100 }], vec![]);
let mut tx = MutableTransaction::with_entries(tx, utxo_entries);
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain();
let tx = tx.as_verifiable();
let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), ctx, Default::default());
assert_eq!(vm.execute(), Ok(()));
}
#[test]
fn test_input_spk_different() {
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&sig_cache).with_reused(&reused_values);
let redeem_script = ScriptBuilder::new().add_ops(&[Op0, OpTxInputSpk, Op1, OpTxInputSpk, OpEqual, OpNot]).unwrap().drain();
let spk = pay_to_script_hash_script(&redeem_script);
let input_mock1 = Kip10Mock { spk, amount: 100 };
let input_mock2 = Kip10Mock { spk: create_mock_spk(2), amount: 100 };
let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock1, input_mock2], vec![]);
let mut tx = MutableTransaction::with_entries(tx, utxo_entries);
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain();
let tx = tx.as_verifiable();
let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), ctx, Default::default());
assert_eq!(vm.execute(), Ok(()));
}
#[test]
fn test_input_spk_same() {
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&sig_cache).with_reused(&reused_values);
let redeem_script = ScriptBuilder::new().add_ops(&[Op0, OpTxInputSpk, Op1, OpTxInputSpk, OpEqual]).unwrap().drain();
let spk = pay_to_script_hash_script(&redeem_script);
let input_mock1 = Kip10Mock { spk: spk.clone(), amount: 100 };
let input_mock2 = Kip10Mock { spk, amount: 100 };
let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock1, input_mock2], vec![]);
let mut tx = MutableTransaction::with_entries(tx, utxo_entries);
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain();
let tx = tx.as_verifiable();
let mut vm = TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), ctx, Default::default());
assert_eq!(vm.execute(), Ok(()));
}
#[test]
fn test_output_spk() {
let expected_spk = create_mock_spk(42);
let expected_spk_bytes = expected_spk.to_bytes();
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&sig_cache).with_reused(&reused_values);
let redeem_script = ScriptBuilder::new()
.add_op(Op0)
.unwrap()
.add_op(OpTxOutputSpk)
.unwrap()
.add_data(&expected_spk_bytes)
.unwrap()
.add_op(OpEqual)
.unwrap()
.drain();
let spk = pay_to_script_hash_script(&redeem_script);
{
let input_mock = Kip10Mock { spk: spk.clone(), amount: 200 };
let output_mock = Kip10Mock { spk: expected_spk.clone(), amount: 100 };
let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock], vec![output_mock]);
let mut tx = MutableTransaction::with_entries(tx, utxo_entries);
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain();
let tx = tx.as_verifiable();
let mut vm =
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), ctx, Default::default());
assert_eq!(vm.execute(), Ok(()));
}
{
let input_mock = Kip10Mock { spk: spk.clone(), amount: 200 };
let output_mock = Kip10Mock {
spk: create_mock_spk(43), amount: 100,
};
let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock], vec![output_mock]);
let mut tx = MutableTransaction::with_entries(tx, utxo_entries);
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain();
let tx = tx.as_verifiable();
let mut vm =
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), ctx, Default::default());
assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse));
}
}
#[test]
fn test_input_index() {
let redeem_script =
ScriptBuilder::new().add_op(OpTxInputIndex).unwrap().add_i64(0).unwrap().add_op(OpEqual).unwrap().drain();
let spk = pay_to_script_hash_script(&redeem_script);
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&sig_cache).with_reused(&reused_values);
{
let input_mock = Kip10Mock { spk: spk.clone(), amount: 200 };
let output_mock = Kip10Mock { spk: create_mock_spk(1), amount: 100 };
let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock], vec![output_mock]);
let mut tx = MutableTransaction::with_entries(tx, utxo_entries);
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain();
let tx = tx.as_verifiable();
let mut vm =
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), ctx, Default::default());
assert_eq!(vm.execute(), Ok(()));
}
{
let input_mock1 = Kip10Mock { spk: create_mock_spk(1), amount: 100 };
let input_mock2 = Kip10Mock { spk: spk.clone(), amount: 200 };
let output_mock = Kip10Mock { spk: create_mock_spk(2), amount: 100 };
let (tx, utxo_entries) = kip_10_tx_mock(vec![input_mock1, input_mock2], vec![output_mock]);
let mut tx = MutableTransaction::with_entries(tx, utxo_entries);
tx.tx.inputs[1].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain();
let tx = tx.as_verifiable();
let mut vm =
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[1], 1, tx.utxo(1).unwrap(), ctx, Default::default());
assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse));
}
}
#[test]
fn test_counts() {
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&sig_cache).with_reused(&reused_values);
let input_count_script =
ScriptBuilder::new().add_op(OpTxInputCount).unwrap().add_i64(2).unwrap().add_op(OpEqual).unwrap().drain();
let output_count_script =
ScriptBuilder::new().add_op(OpTxOutputCount).unwrap().add_i64(3).unwrap().add_op(OpEqual).unwrap().drain();
let input_spk = pay_to_script_hash_script(&input_count_script);
let output_spk = pay_to_script_hash_script(&output_count_script);
let input_mock1 = Kip10Mock { spk: input_spk.clone(), amount: 100 };
let input_mock2 = Kip10Mock { spk: output_spk.clone(), amount: 200 };
let output_mock1 = Kip10Mock { spk: create_mock_spk(1), amount: 50 };
let output_mock2 = Kip10Mock { spk: create_mock_spk(2), amount: 100 };
let output_mock3 = Kip10Mock { spk: create_mock_spk(3), amount: 150 };
let (tx, utxo_entries) =
kip_10_tx_mock(vec![input_mock1.clone(), input_mock2.clone()], vec![output_mock1, output_mock2, output_mock3]);
{
let mut tx = MutableTransaction::with_entries(tx.clone(), utxo_entries.clone());
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&input_count_script).unwrap().drain();
let tx = tx.as_verifiable();
let mut vm =
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), ctx, Default::default());
assert_eq!(vm.execute(), Ok(()));
}
{
let mut tx = MutableTransaction::with_entries(tx.clone(), utxo_entries.clone());
tx.tx.inputs[1].signature_script = ScriptBuilder::new().add_data(&output_count_script).unwrap().drain();
let tx = tx.as_verifiable();
let mut vm =
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[1], 1, tx.utxo(1).unwrap(), ctx, Default::default());
assert_eq!(vm.execute(), Ok(()));
}
{
let wrong_input_count_script =
ScriptBuilder::new().add_op(OpTxInputCount).unwrap().add_i64(3).unwrap().add_op(OpEqual).unwrap().drain();
let mut tx = MutableTransaction::with_entries(tx.clone(), utxo_entries.clone());
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&wrong_input_count_script).unwrap().drain();
let tx = tx.as_verifiable();
let mut vm =
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), ctx, Default::default());
assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse));
}
{
let wrong_output_count_script =
ScriptBuilder::new().add_op(OpTxOutputCount).unwrap().add_i64(2).unwrap().add_op(OpEqual).unwrap().drain();
let mut tx = MutableTransaction::with_entries(tx.clone(), utxo_entries.clone());
tx.tx.inputs[1].signature_script = ScriptBuilder::new().add_data(&wrong_output_count_script).unwrap().drain();
let tx = tx.as_verifiable();
let mut vm =
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[1], 1, tx.utxo(1).unwrap(), ctx, Default::default());
assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse));
}
}
}
#[cfg(test)]
mod introspection {
use super::*;
use crate::covenants::CovenantsContext;
use crate::script_builder::{ScriptBuilder, ScriptBuilderResult};
use crate::{EngineCtx, MAX_SCRIPT_ELEMENT_SIZE, pay_to_script_hash_script};
use crate::{SpkEncoding, opcodes::codes};
use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync;
use kaspa_consensus_core::subnets::SubnetworkId;
use kaspa_consensus_core::tx::{
CovenantBinding, MutableTransaction, PopulatedTransaction, ScriptPublicKey, Transaction, TransactionInput,
TransactionOutpoint, TransactionOutput, UtxoEntry,
};
use kaspa_hashes::{Hash, ZERO_HASH};
use kaspa_txscript_errors::CovenantsError;
fn payload_bytes(len: usize) -> Vec<u8> {
(0..len).map(|i| (i % 256) as u8).collect()
}
fn base_transaction(payload_len: usize) -> (Transaction, Vec<UtxoEntry>) {
let version: u16 = 5;
let lock_time: u64 = 123;
let subnetwork_id = SubnetworkId::from_bytes([9u8; 20]);
let gas: u64 = 777;
let payload = payload_bytes(payload_len);
let sig_script_0 = ScriptBuilder::new().add_data(&[0xaa, 0xbb, 0xcc]).expect("sig script build").drain();
let sig_script_1 = ScriptBuilder::new().add_data(&[0x11]).expect("sig script build").drain();
let inputs = vec![
TransactionInput::new(TransactionOutpoint::new(Hash::default(), 0), sig_script_0, 0x1122334455667788, 0),
TransactionInput::new(TransactionOutpoint::new(Hash::default(), 1), sig_script_1, 0x0fedcba987654321, 0),
];
let output_spk_0 = ScriptBuilder::new().add_ops(&[codes::OpTrue, codes::Op2, codes::Op3]).expect("spk build").drain();
let output_spk_1 = ScriptBuilder::new().add_ops(&[codes::Op4, codes::Op5]).expect("spk build").drain();
let outputs = vec![
TransactionOutput::new(11, ScriptPublicKey::new(0, output_spk_0.into())),
TransactionOutput::new(22, ScriptPublicKey::new(0, output_spk_1.into())),
];
let mut tx = Transaction::new(version, inputs, outputs, lock_time, subnetwork_id, gas, payload);
tx.finalize();
let utxo_spk_0 = ScriptBuilder::new().add_ops(&[codes::OpTrue, codes::Op1]).expect("spk build").drain();
let utxo_spk_1 = ScriptBuilder::new().add_ops(&[codes::Op2, codes::Op3]).expect("spk build").drain();
let entries = vec![
UtxoEntry::new(1000, ScriptPublicKey::new(0, utxo_spk_0.into()), 0, true, None),
UtxoEntry::new(2000, ScriptPublicKey::new(0, utxo_spk_1.into()), 0, false, None),
];
(tx, entries)
}
fn create_transaction_with_covenant() -> (Transaction, Vec<UtxoEntry>) {
let version: u16 = 5;
let lock_time: u64 = 0;
let subnetwork_id = SubnetworkId::from_bytes([9u8; 20]);
let gas: u64 = 0;
let payload = payload_bytes(0);
let sig_script = ScriptBuilder::new().add_op(codes::OpTrue).expect("sig script build").drain();
let inputs = vec![
TransactionInput::new(TransactionOutpoint::new(Hash::default(), 0), sig_script.clone(), 0, 0),
TransactionInput::new(TransactionOutpoint::new(Hash::default(), 1), sig_script.clone(), 0, 0),
TransactionInput::new(TransactionOutpoint::new(Hash::default(), 2), sig_script.clone(), 0, 0),
TransactionInput::new(TransactionOutpoint::new(Hash::default(), 3), sig_script.clone(), 0, 0),
TransactionInput::new(TransactionOutpoint::new(Hash::default(), 4), sig_script, 0, 0),
];
let spk = ScriptBuilder::new().add_op(codes::OpTrue).expect("spk build").drain();
let outputs = vec![
TransactionOutput {
value: 11,
script_public_key: ScriptPublicKey::new(0, spk.clone().into()),
covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: Hash::from_u64_word(1) }),
},
TransactionOutput {
value: 22,
script_public_key: ScriptPublicKey::new(0, spk.clone().into()),
covenant: Some(CovenantBinding { authorizing_input: 3, covenant_id: Hash::from_u64_word(1) }),
},
TransactionOutput {
value: 33,
script_public_key: ScriptPublicKey::new(0, spk.clone().into()),
covenant: Some(CovenantBinding { authorizing_input: 1, covenant_id: Hash::from_u64_word(2) }),
},
TransactionOutput::new(44, ScriptPublicKey::new(0, spk.clone().into())),
TransactionOutput {
value: 55,
script_public_key: ScriptPublicKey::new(0, spk.clone().into()),
covenant: Some(CovenantBinding { authorizing_input: 3, covenant_id: Hash::from_u64_word(1) }),
},
TransactionOutput {
value: 66,
script_public_key: ScriptPublicKey::new(0, spk.into()),
covenant: Some(CovenantBinding { authorizing_input: 4, covenant_id: Hash::from_u64_word(2) }),
},
];
let mut tx = Transaction::new(version, inputs, outputs, lock_time, subnetwork_id, gas, payload);
tx.finalize();
let utxo_spk = ScriptBuilder::new().add_op(codes::OpTrue).expect("spk build").drain();
let entries = vec![
UtxoEntry::new(1000, ScriptPublicKey::new(0, utxo_spk.clone().into()), 0, false, Some(Hash::from_u64_word(1))),
UtxoEntry::new(1000, ScriptPublicKey::new(0, utxo_spk.clone().into()), 0, false, Some(Hash::from_u64_word(2))),
UtxoEntry::new(1000, ScriptPublicKey::new(0, utxo_spk.clone().into()), 0, false, Some(Hash::from_u64_word(1))),
UtxoEntry::new(1000, ScriptPublicKey::new(0, utxo_spk.clone().into()), 0, false, Some(Hash::from_u64_word(1))),
UtxoEntry::new(1000, ScriptPublicKey::new(0, utxo_spk.into()), 0, false, Some(Hash::from_u64_word(2))),
];
(tx, entries)
}
fn script<F>(build: F) -> Vec<u8>
where
F: FnOnce(&mut ScriptBuilder) -> ScriptBuilderResult<&mut ScriptBuilder>,
{
let mut sb = ScriptBuilder::new();
sb.add_op(codes::OpDrop).expect("builder failure");
build(&mut sb).expect("builder failure");
sb.drain()
}
fn run_script(tx: &Transaction, mut entries: Vec<UtxoEntry>, idx: usize, script: Vec<u8>) -> Result<(), TxScriptError> {
entries[idx].script_public_key = ScriptPublicKey::new(0, script.into());
let populated_tx = PopulatedTransaction::new(tx, entries);
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let covenants_ctx = CovenantsContext::from_tx(&populated_tx)?;
let ctx = EngineContext::new(&sig_cache).with_reused(&reused_values).with_covenants_ctx(&covenants_ctx);
let mut vm = TxScriptEngine::from_transaction_input(
&populated_tx,
&populated_tx.tx.inputs[idx],
idx,
&populated_tx.entries[idx],
ctx,
Default::default(),
);
vm.execute()
}
#[test]
fn tx_level_introspection() {
let (tx, entries) = base_transaction(100);
let (tx_large, entries_large) = base_transaction(600);
let expected_subnet: Vec<u8> = tx.subnetwork_id.into();
let spk_version = script(|sb| sb.add_op(codes::OpTxVersion)?.add_i64(tx.version as i64)?.add_op(codes::OpEqual));
run_script(&tx, entries.clone(), 0, spk_version).expect("tx version");
let spk_lock_time = script(|sb| sb.add_op(codes::OpTxLockTime)?.add_i64(tx.lock_time as i64)?.add_op(codes::OpEqual));
run_script(&tx, entries.clone(), 0, spk_lock_time).expect("tx locktime");
let spk_subnet = script(|sb| sb.add_op(codes::OpTxSubnetId)?.add_data(&expected_subnet)?.add_op(codes::OpEqual));
run_script(&tx, entries.clone(), 0, spk_subnet).expect("tx subnet id");
let spk_gas = script(|sb| sb.add_op(codes::OpTxGas)?.add_i64(tx.gas as i64)?.add_op(codes::OpEqual));
run_script(&tx, entries.clone(), 0, spk_gas).expect("tx gas");
let spk_payload_len =
script(|sb| sb.add_op(codes::OpTxPayloadLen)?.add_i64(tx.payload.len() as i64)?.add_op(codes::OpEqual));
run_script(&tx, entries.clone(), 0, spk_payload_len).expect("tx payload len");
let expected_payload_full = tx.payload.clone();
let spk_payload_substr_full = script(|sb| {
sb.add_i64(0)?
.add_op(codes::OpTxPayloadLen)?
.add_op(codes::OpTxPayloadSubstr)?
.add_data(&expected_payload_full)?
.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_payload_substr_full).expect("payload substr full");
let expected_substr = tx.payload[1..4].to_vec();
let spk_payload_substr = script(|sb| {
sb.add_i64(1)?.add_i64(4)?.add_op(codes::OpTxPayloadSubstr)?.add_data(&expected_substr)?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_payload_substr).expect("payload substr ok");
let spk_payload_substr_oob = script(|sb| sb.add_i64(1000)?.add_i64(1002)?.add_op(codes::OpTxPayloadSubstr));
let err =
run_script(&tx_large, entries_large.clone(), 0, spk_payload_substr_oob).expect_err("payload substr out of bounds");
assert!(matches!(err, TxScriptError::OutOfBoundsSubstring(_, _, _)));
let spk_payload_substr_too_long =
script(|sb| sb.add_i64(0)?.add_i64(MAX_SCRIPT_ELEMENT_SIZE as i64 + 1)?.add_op(codes::OpTxPayloadSubstr));
let err = run_script(&tx_large, entries_large.clone(), 0, spk_payload_substr_too_long).expect_err("payload substr >520");
assert!(matches!(err, TxScriptError::ElementTooBig(_, _)));
}
#[test]
fn input_input_output_introspection() {
let (tx, entries) = base_transaction(40);
let input_spk_bytes_1 = entries[1].script_public_key.to_bytes();
let out_spk_bytes_0 = tx.outputs[0].script_public_key.to_bytes();
let sig_script_0 = tx.inputs[0].signature_script.clone();
let spk_input_spk_len = script(|sb| {
sb.add_i64(1)?.add_op(codes::OpTxInputSpkLen)?.add_i64(input_spk_bytes_1.len() as i64)?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_input_spk_len).expect("input spk len");
let expected_spk_full = input_spk_bytes_1.clone();
let spk_input_spk_substr_full = script(|sb| {
sb.add_i64(1)?
.add_i64(0)?
.add_i64(1)?
.add_op(codes::OpTxInputSpkLen)?
.add_op(codes::OpTxInputSpkSubstr)?
.add_data(&expected_spk_full)?
.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_input_spk_substr_full).expect("input spk substr full");
let expected_spk_suffix = input_spk_bytes_1[1..].to_vec();
let spk_input_spk_substr_suffix = script(|sb| {
sb.add_i64(1)?
.add_i64(1)?
.add_i64(1)?
.add_op(codes::OpTxInputSpkLen)?
.add_op(codes::OpTxInputSpkSubstr)?
.add_data(&expected_spk_suffix)?
.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_input_spk_substr_suffix).expect("input spk substr suffix");
let spk_input_spk_substr_oob = script(|sb| sb.add_i64(1)?.add_i64(100)?.add_i64(101)?.add_op(codes::OpTxInputSpkSubstr));
let err = run_script(&tx, entries.clone(), 0, spk_input_spk_substr_oob).expect_err("input spk substr oob");
assert!(matches!(err, TxScriptError::OutOfBoundsSubstring(_, _, _)));
let spk_output_spk_len = script(|sb| {
sb.add_i64(0)?.add_op(codes::OpTxOutputSpkLen)?.add_i64(out_spk_bytes_0.len() as i64)?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_output_spk_len).expect("output spk len");
let expected_out_spk_full = out_spk_bytes_0.clone();
let spk_output_spk_substr_full = script(|sb| {
sb.add_i64(0)?
.add_i64(0)?
.add_i64(0)?
.add_op(codes::OpTxOutputSpkLen)?
.add_op(codes::OpTxOutputSpkSubstr)?
.add_data(&expected_out_spk_full)?
.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_output_spk_substr_full).expect("output spk substr full");
let expected_out_spk_tail = out_spk_bytes_0[1..3].to_vec();
let spk_output_spk_substr_tail = script(|sb| {
sb.add_i64(0)?
.add_i64(1)?
.add_i64(3)?
.add_op(codes::OpTxOutputSpkSubstr)?
.add_data(&expected_out_spk_tail)?
.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_output_spk_substr_tail).expect("output spk substr tail");
let spk_input_sig_len = script(|sb| {
sb.add_i64(0)?.add_op(codes::OpTxInputScriptSigLen)?.add_i64(sig_script_0.len() as i64)?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_input_sig_len).expect("input sig len");
let expected_sig_full = sig_script_0.clone();
let spk_input_sig_substr_full = script(|sb| {
sb.add_i64(0)?
.add_i64(0)?
.add_i64(0)?
.add_op(codes::OpTxInputScriptSigLen)?
.add_op(codes::OpTxInputScriptSigSubstr)?
.add_data(&expected_sig_full)?
.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_input_sig_substr_full).expect("input sig substr full");
let expected_sig_substr = sig_script_0[1..3].to_vec();
let spk_input_sig_substr = script(|sb| {
sb.add_i64(0)?
.add_i64(1)?
.add_i64(3)?
.add_op(codes::OpTxInputScriptSigSubstr)?
.add_data(&expected_sig_substr)?
.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_input_sig_substr).expect("input sig substr");
let spk_outpoint_txid = script(|sb| {
sb.add_i64(1)?
.add_op(codes::OpOutpointTxId)?
.add_data(&tx.inputs[1].previous_outpoint.transaction_id.as_bytes())?
.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_outpoint_txid).expect("outpoint txid");
let spk_outpoint_index = script(|sb| sb.add_i64(1)?.add_op(codes::OpOutpointIndex)?.add_i64(1)?.add_op(codes::OpEqual));
run_script(&tx, entries.clone(), 0, spk_outpoint_index).expect("outpoint index");
let seq_bytes = tx.inputs[0].sequence.to_le_bytes();
let spk_seq = script(|sb| sb.add_i64(0)?.add_op(codes::OpTxInputSeq)?.add_data(&seq_bytes)?.add_op(codes::OpEqual));
run_script(&tx, entries.clone(), 0, spk_seq).expect("input seq");
let spk_is_coinbase_true =
script(|sb| sb.add_i64(0)?.add_op(codes::OpTxInputIsCoinbase)?.add_i64(1)?.add_op(codes::OpEqual));
run_script(&tx, entries.clone(), 0, spk_is_coinbase_true).expect("is coinbase true");
let spk_is_coinbase_false =
script(|sb| sb.add_i64(1)?.add_op(codes::OpTxInputIsCoinbase)?.add_i64(0)?.add_op(codes::OpEqual));
run_script(&tx, entries, 0, spk_is_coinbase_false).expect("is coinbase false");
}
#[test]
fn cov_output_count() {
let (tx, entries) = create_transaction_with_covenant();
for (input_idx, expected_count) in [(0, 1), (1, 1), (2, 0), (3, 2), (4, 1)] {
let spk = script(|sb| {
sb.add_i64(input_idx)?.add_op(codes::OpAuthOutputCount)?.add_i64(expected_count)?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk).expect("cov output count");
}
let spk_invalid = script(|sb| sb.add_i64(7)?.add_op(codes::OpAuthOutputCount));
let err = run_script(&tx, entries, 0, spk_invalid).expect_err("cov output count invalid input");
assert!(matches!(err, TxScriptError::InvalidInputIndex(7, 5)));
}
#[test]
fn auth_output_idx() {
let (tx, entries) = create_transaction_with_covenant();
for (input_idx, authorized_idx, expected_output_idx) in [(0, 0, 0), (1, 0, 2), (3, 0, 1), (3, 1, 4), (4, 0, 5)] {
let spk = script(|sb| {
sb.add_i64(input_idx)?
.add_i64(authorized_idx)?
.add_op(codes::OpAuthOutputIdx)?
.add_i64(expected_output_idx)?
.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk).unwrap();
}
let spk_missing = script(|sb| sb.add_i64(0)?.add_i64(2)?.add_op(codes::OpAuthOutputIdx));
let err = run_script(&tx, entries, 0, spk_missing).expect_err("cov output idx missing");
assert!(matches!(err, TxScriptError::CovenantsError(CovenantsError::InvalidAuthCovOutIndex(2, 0, 1))));
}
#[test]
fn covenant_id_opcodes() {
let (tx, entries) = create_transaction_with_covenant();
let covenant_id_1 = Hash::from_u64_word(1);
let covenant_id_2 = Hash::from_u64_word(2);
let covenant_id_3 = Hash::from_u64_word(3);
let spk_input_cov_id_0 = script(|sb| {
sb.add_i64(0)?.add_op(codes::OpInputCovenantId)?.add_data(&covenant_id_1.as_bytes())?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_input_cov_id_0).unwrap();
let spk_input_cov_id_1 = script(|sb| {
sb.add_i64(1)?.add_op(codes::OpInputCovenantId)?.add_data(&covenant_id_2.as_bytes())?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_input_cov_id_1).unwrap();
let spk_input_cov_id_3 = script(|sb| {
sb.add_i64(3)?.add_op(codes::OpInputCovenantId)?.add_data(&covenant_id_1.as_bytes())?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_input_cov_id_3).unwrap();
let spk_output_cov_id_0 = script(|sb| {
sb.add_i64(0)?.add_op(codes::OpOutputCovenantId)?.add_data(&covenant_id_1.as_bytes())?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_output_cov_id_0).unwrap();
let spk_output_cov_id_2 = script(|sb| {
sb.add_i64(2)?.add_op(codes::OpOutputCovenantId)?.add_data(&covenant_id_2.as_bytes())?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_output_cov_id_2).unwrap();
let spk_output_auth_input_0 =
script(|sb| sb.add_i64(0)?.add_op(codes::OpOutputAuthorizingInput)?.add_i64(0)?.add_op(codes::OpEqual));
run_script(&tx, entries.clone(), 0, spk_output_auth_input_0).unwrap();
let spk_output_auth_input_1 =
script(|sb| sb.add_i64(1)?.add_op(codes::OpOutputAuthorizingInput)?.add_i64(3)?.add_op(codes::OpEqual));
run_script(&tx, entries.clone(), 0, spk_output_auth_input_1).unwrap();
let spk_cov_in_count_1 = script(|sb| {
sb.add_data(&covenant_id_1.as_bytes())?.add_op(codes::OpCovInputCount)?.add_i64(3)?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_cov_in_count_1).unwrap();
let spk_cov_in_count_2 = script(|sb| {
sb.add_data(&covenant_id_2.as_bytes())?.add_op(codes::OpCovInputCount)?.add_i64(2)?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_cov_in_count_2).unwrap();
let spk_cov_in_count_0 = script(|sb| {
sb.add_data(&covenant_id_3.as_bytes())?.add_op(codes::OpCovInputCount)?.add_i64(0)?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_cov_in_count_0).unwrap();
let spk_cov_in_idx_0 = script(|sb| {
sb.add_data(&covenant_id_1.as_bytes())?.add_i64(0)?.add_op(codes::OpCovInputIdx)?.add_i64(0)?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_cov_in_idx_0).unwrap();
let spk_cov_in_idx_3 = script(|sb| {
sb.add_data(&covenant_id_1.as_bytes())?.add_i64(2)?.add_op(codes::OpCovInputIdx)?.add_i64(3)?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_cov_in_idx_3).unwrap();
let spk_cov_in_idx_oob = script(|sb| sb.add_data(&covenant_id_1.as_bytes())?.add_i64(3)?.add_op(codes::OpCovInputIdx));
let err = run_script(&tx, entries.clone(), 0, spk_cov_in_idx_oob).expect_err("cov input idx oob");
assert!(matches!(err, TxScriptError::CovenantsError(CovenantsError::InvalidCovInIndex(_, _))));
let spk_cov_out_count_1 = script(|sb| {
sb.add_data(&covenant_id_1.as_bytes())?.add_op(codes::OpCovOutputCount)?.add_i64(3)?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_cov_out_count_1).unwrap();
let spk_cov_out_count_2 = script(|sb| {
sb.add_data(&covenant_id_2.as_bytes())?.add_op(codes::OpCovOutputCount)?.add_i64(2)?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_cov_out_count_2).unwrap();
let spk_cov_out_count_0 = script(|sb| {
sb.add_data(&covenant_id_3.as_bytes())?.add_op(codes::OpCovOutputCount)?.add_i64(0)?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_cov_out_count_0).unwrap();
let spk_cov_out_idx_0 = script(|sb| {
sb.add_data(&covenant_id_1.as_bytes())?.add_i64(0)?.add_op(codes::OpCovOutputIdx)?.add_i64(0)?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_cov_out_idx_0).unwrap();
let spk_cov_out_idx_4 = script(|sb| {
sb.add_data(&covenant_id_1.as_bytes())?.add_i64(2)?.add_op(codes::OpCovOutputIdx)?.add_i64(4)?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_cov_out_idx_4).unwrap();
let spk_cov_out_idx_5 = script(|sb| {
sb.add_data(&covenant_id_2.as_bytes())?.add_i64(1)?.add_op(codes::OpCovOutputIdx)?.add_i64(5)?.add_op(codes::OpEqual)
});
run_script(&tx, entries.clone(), 0, spk_cov_out_idx_5).unwrap();
let spk_cov_out_idx_oob = script(|sb| sb.add_data(&covenant_id_1.as_bytes())?.add_i64(3)?.add_op(codes::OpCovOutputIdx));
let err = run_script(&tx, entries.clone(), 0, spk_cov_out_idx_oob).expect_err("cov out idx oob");
assert!(matches!(err, TxScriptError::CovenantsError(CovenantsError::InvalidCovOutIndex(_, _))));
let spk_output_cov_id_oob = script(|sb| sb.add_i64(6)?.add_op(codes::OpOutputCovenantId));
let err = run_script(&tx, entries.clone(), 0, spk_output_cov_id_oob).expect_err("output covenant id oob");
assert_eq!(err, TxScriptError::InvalidOutputIndex(6, 6));
let spk_output_auth_input_oob = script(|sb| sb.add_i64(6)?.add_op(codes::OpOutputAuthorizingInput));
let err = run_script(&tx, entries, 0, spk_output_auth_input_oob).expect_err("output authorizing input oob");
assert_eq!(err, TxScriptError::InvalidOutputIndex(6, 6));
let (tx_no_cov, entries_no_cov) = base_transaction(0);
let spk_input_cov_none =
script(|sb| sb.add_i64(0)?.add_op(codes::OpInputCovenantId)?.add_data(&ZERO_HASH.as_bytes())?.add_op(codes::OpEqual));
run_script(&tx_no_cov, entries_no_cov.clone(), 0, spk_input_cov_none).unwrap();
let spk_output_cov_none =
script(|sb| sb.add_i64(0)?.add_op(codes::OpOutputCovenantId)?.add_data(&ZERO_HASH.as_bytes())?.add_op(codes::OpEqual));
run_script(&tx_no_cov, entries_no_cov.clone(), 0, spk_output_cov_none).unwrap();
let spk_output_auth_none =
script(|sb| sb.add_i64(0)?.add_op(codes::OpOutputAuthorizingInput)?.add_i64(-1)?.add_op(codes::OpEqual));
run_script(&tx_no_cov, entries_no_cov.clone(), 0, spk_output_auth_none).unwrap();
let spk_input_cov_oob = script(|sb| sb.add_i64(5)?.add_op(codes::OpInputCovenantId));
let err = run_script(&tx_no_cov, entries_no_cov, 0, spk_input_cov_oob).expect_err("input covenant id oob");
assert!(matches!(err, TxScriptError::InvalidInputIndex(5, 2)));
}
#[test]
fn error_paths_for_indices_and_sizes() {
let (tx, entries) = base_transaction(600);
let spk_bad_input_index = script(|sb| sb.add_i64(5)?.add_op(codes::OpTxInputSpkLen));
let err = run_script(&tx, entries.clone(), 0, spk_bad_input_index).expect_err("invalid input index");
assert!(matches!(err, TxScriptError::InvalidInputIndex(_, _)));
let spk_bad_output_index = script(|sb| sb.add_i64(9)?.add_op(codes::OpTxOutputSpkLen));
let err = run_script(&tx, entries.clone(), 0, spk_bad_output_index).expect_err("invalid output index");
assert!(matches!(err, TxScriptError::InvalidOutputIndex(_, _)));
let mut large_entries = entries.clone();
large_entries[1].script_public_key = ScriptPublicKey::new(0, vec![0u8; MAX_SCRIPT_ELEMENT_SIZE + 1].into());
let spk_large_spk_substr =
script(|sb| sb.add_i64(1)?.add_i64(0)?.add_i64(MAX_SCRIPT_ELEMENT_SIZE as i64 + 1)?.add_op(codes::OpTxInputSpkSubstr));
let err = run_script(&tx, large_entries.clone(), 0, spk_large_spk_substr).expect_err("input spk substr too long");
assert!(matches!(err, TxScriptError::ElementTooBig(_, _)));
}
#[test]
fn test_op_tx_input_daa_score() {
for (input_idx, expected_daa_score) in [(0, 12345), (1, 67890), (2, 99999)] {
let mut redeem_script = ScriptBuilder::new();
redeem_script
.add_i64(input_idx)
.unwrap()
.add_op(codes::OpTxInputDaaScore)
.unwrap()
.add_i64(expected_daa_score)
.unwrap()
.add_op(codes::OpEqual)
.unwrap();
let redeem_script = redeem_script.drain();
let spk = pay_to_script_hash_script(&redeem_script);
let inputs = vec![
TransactionInput::new(TransactionOutpoint::new(Hash::default(), 0), vec![0xaa], 0, 0),
TransactionInput::new(TransactionOutpoint::new(Hash::default(), 1), vec![0xbb], 0, 0),
TransactionInput::new(TransactionOutpoint::new(Hash::default(), 2), vec![0xcc], 0, 0),
];
let output_spk = ScriptBuilder::new().add_op(codes::OpTrue).expect("spk build").drain();
let outputs = vec![TransactionOutput::new(100, ScriptPublicKey::new(0, output_spk.into()))];
let mut tx = Transaction::new(1, inputs, outputs, 0, Default::default(), 0, vec![]);
tx.finalize();
let input_spk = ScriptBuilder::new().add_op(codes::OpTrue).expect("spk build").drain();
let entries = vec![
UtxoEntry::new(1000, ScriptPublicKey::new(0, input_spk.clone().into()), 12345, false, None),
UtxoEntry::new(2000, ScriptPublicKey::new(0, input_spk.clone().into()), 67890, false, None),
UtxoEntry::new(3000, ScriptPublicKey::new(0, input_spk.into()), 99999, false, None),
];
let mut tx = MutableTransaction::with_entries(tx, entries.clone());
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain();
tx.entries[0].as_mut().unwrap().script_public_key = spk;
let tx = tx.as_verifiable();
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let mut vm = TxScriptEngine::from_transaction_input(
&tx,
&tx.inputs()[0],
0,
tx.utxo(0).unwrap(),
EngineCtx::new(&sig_cache).with_reused(&reused_values),
Default::default(),
);
vm.execute().unwrap_or_else(|_| panic!("input {} daa score", input_idx));
}
{
let mut redeem_script = ScriptBuilder::new();
redeem_script.add_i64(-1).unwrap().add_op(codes::OpTxInputDaaScore).unwrap();
let redeem_script = redeem_script.drain();
let spk = pay_to_script_hash_script(&redeem_script);
let input = TransactionInput::new(TransactionOutpoint::new(Hash::default(), 0), vec![], 0, 0);
let output = TransactionOutput::new(100, ScriptPublicKey::new(0, vec![codes::OpTrue].into()));
let mut tx = Transaction::new(1, vec![input], vec![output], 0, Default::default(), 0, vec![]);
tx.finalize();
let utxo_entry = UtxoEntry::new(1000, spk, 12345, false, None);
let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry]);
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain();
let tx = tx.as_verifiable();
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let mut vm = TxScriptEngine::from_transaction_input(
&tx,
&tx.inputs()[0],
0,
tx.utxo(0).unwrap(),
EngineCtx::new(&sig_cache).with_reused(&reused_values),
Default::default(),
);
let err = vm.execute().expect_err("should fail with negative index");
assert!(matches!(err, TxScriptError::InvalidIndex(-1)));
}
{
let mut redeem_script = ScriptBuilder::new();
redeem_script.add_i64(3).unwrap().add_op(codes::OpTxInputDaaScore).unwrap();
let redeem_script = redeem_script.drain();
let spk = pay_to_script_hash_script(&redeem_script);
let inputs = vec![
TransactionInput::new(TransactionOutpoint::new(Hash::default(), 0), vec![], 0, 0),
TransactionInput::new(TransactionOutpoint::new(Hash::default(), 1), vec![], 0, 0),
TransactionInput::new(TransactionOutpoint::new(Hash::default(), 2), vec![], 0, 0),
];
let output = TransactionOutput::new(100, ScriptPublicKey::new(0, vec![codes::OpTrue].into()));
let mut tx = Transaction::new(1, inputs, vec![output], 0, Default::default(), 0, vec![]);
tx.finalize();
let input_spk = ScriptBuilder::new().add_op(codes::OpTrue).expect("spk build").drain();
let entries = vec![
UtxoEntry::new(1000, spk, 12345, false, None),
UtxoEntry::new(2000, ScriptPublicKey::new(0, input_spk.clone().into()), 67890, false, None),
UtxoEntry::new(3000, ScriptPublicKey::new(0, input_spk.into()), 99999, false, None),
];
let mut tx = MutableTransaction::with_entries(tx, entries);
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain();
let tx = tx.as_verifiable();
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let mut vm = TxScriptEngine::from_transaction_input(
&tx,
&tx.inputs()[0],
0,
tx.utxo(0).unwrap(),
EngineCtx::new(&sig_cache).with_reused(&reused_values),
Default::default(),
);
let err = vm.execute().expect_err("should fail with out of bounds index");
assert!(matches!(err, TxScriptError::InvalidInputIndex(3, 3)));
}
{
let mut redeem_script = ScriptBuilder::new();
redeem_script
.add_i64(0)
.unwrap()
.add_op(codes::OpTxInputDaaScore)
.unwrap()
.add_i64(1)
.unwrap()
.add_op(codes::OpTxInputDaaScore)
.unwrap()
.add_op(codes::OpLessThan)
.unwrap(); let redeem_script = redeem_script.drain();
let spk = pay_to_script_hash_script(&redeem_script);
let inputs = vec![
TransactionInput::new(TransactionOutpoint::new(Hash::default(), 0), vec![], 0, 0),
TransactionInput::new(TransactionOutpoint::new(Hash::default(), 1), vec![], 0, 0),
];
let output = TransactionOutput::new(100, ScriptPublicKey::new(0, vec![codes::OpTrue].into()));
let mut tx = Transaction::new(1, inputs, vec![output], 0, Default::default(), 0, vec![]);
tx.finalize();
let input_spk = ScriptBuilder::new().add_op(codes::OpTrue).expect("spk build").drain();
let entries = vec![
UtxoEntry::new(1000, spk, 12345, false, None),
UtxoEntry::new(2000, ScriptPublicKey::new(0, input_spk.into()), 67890, false, None),
];
let mut tx = MutableTransaction::with_entries(tx, entries);
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain();
let tx = tx.as_verifiable();
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let mut vm = TxScriptEngine::from_transaction_input(
&tx,
&tx.inputs()[0],
0,
tx.utxo(0).unwrap(),
EngineCtx::new(&sig_cache).with_reused(&reused_values),
Default::default(),
);
vm.execute().expect("compare daa scores");
}
}
#[test]
fn test_op_tx_input_daa_score_in_script() {
let sig_cache = Cache::new(10_000);
let reused_values = SigHashReusedValuesUnsync::new();
let ctx = EngineContext::new(&sig_cache).with_reused(&reused_values);
let redeem_script = ScriptBuilder::new()
.add_i64(0)
.unwrap()
.add_op(codes::OpTxInputDaaScore)
.unwrap()
.add_i64(50000)
.unwrap()
.add_op(codes::OpGreaterThanOrEqual)
.unwrap()
.drain();
let spk = pay_to_script_hash_script(&redeem_script);
{
let input = TransactionInput::new(TransactionOutpoint::new(Hash::default(), 0), vec![], 0, 0);
let output = TransactionOutput::new(100, ScriptPublicKey::new(0, vec![codes::OpTrue].into()));
let mut tx = Transaction::new(1, vec![input.clone()], vec![output], 0, Default::default(), 0, vec![]);
tx.finalize();
let utxo_entry = UtxoEntry::new(1000, spk.clone(), 60000, false, None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]);
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain();
let tx = tx.as_verifiable();
let mut vm =
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), ctx, Default::default());
assert_eq!(vm.execute(), Ok(()), "Should pass with DAA score 60000 >= 50000");
}
{
let input = TransactionInput::new(TransactionOutpoint::new(Hash::default(), 0), vec![], 0, 0);
let output = TransactionOutput::new(100, ScriptPublicKey::new(0, vec![codes::OpTrue].into()));
let mut tx = Transaction::new(1, vec![input.clone()], vec![output], 0, Default::default(), 0, vec![]);
tx.finalize();
let utxo_entry = UtxoEntry::new(1000, spk.clone(), 40000, false, None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]);
tx.tx.inputs[0].signature_script = ScriptBuilder::new().add_data(&redeem_script).unwrap().drain();
let tx = tx.as_verifiable();
let mut vm =
TxScriptEngine::from_transaction_input(&tx, &tx.inputs()[0], 0, tx.utxo(0).unwrap(), ctx, Default::default());
assert_eq!(vm.execute(), Err(TxScriptError::EvalFalse), "Should fail with DAA score 40000 < 50000");
}
}
}
}