use crate::ristretto::RistrettoPublicKey;
use std::{fmt, ops::Deref};
use tari_utilities::{hex::Hex, ByteArray};
use super::ScriptError;
pub type HashValue = [u8; 32];
pub type Message = [u8; 32];
pub fn slice_to_hash(slice: &[u8]) -> HashValue {
let mut hash = [0u8; 32];
hash.copy_from_slice(slice);
hash
}
pub fn slice_to_boxed_hash(slice: &[u8]) -> Box<HashValue> {
Box::new(slice_to_hash(slice))
}
pub fn slice_to_message(slice: &[u8]) -> Message {
let mut msg = [0u8; 32];
msg.copy_from_slice(slice);
msg
}
pub fn slice_to_boxed_message(slice: &[u8]) -> Box<Message> {
Box::new(slice_to_message(slice))
}
fn slice_to_u64(slice: &[u8]) -> u64 {
let mut num = [0u8; 8];
num.copy_from_slice(slice);
u64::from_le_bytes(num)
}
fn slice_to_i64(slice: &[u8]) -> i64 {
let mut num = [0u8; 8];
num.copy_from_slice(slice);
i64::from_le_bytes(num)
}
pub const OP_CHECK_HEIGHT_VERIFY: u8 = 0x66;
pub const OP_CHECK_HEIGHT: u8 = 0x67;
pub const OP_COMPARE_HEIGHT_VERIFY: u8 = 0x68;
pub const OP_COMPARE_HEIGHT: u8 = 0x69;
pub const OP_DROP: u8 = 0x70;
pub const OP_DUP: u8 = 0x71;
pub const OP_REV_ROT: u8 = 0x72;
pub const OP_PUSH_HASH: u8 = 0x7a;
pub const OP_PUSH_ZERO: u8 = 0x7b;
pub const OP_NOP: u8 = 0x73;
pub const OP_PUSH_ONE: u8 = 0x7c;
pub const OP_PUSH_INT: u8 = 0x7d;
pub const OP_PUSH_PUBKEY: u8 = 0x7e;
pub const OP_EQUAL: u8 = 0x80;
pub const OP_EQUAL_VERIFY: u8 = 0x81;
pub const OP_ADD: u8 = 0x93;
pub const OP_SUB: u8 = 0x94;
pub const OP_GE_ZERO: u8 = 0x82;
pub const OP_GT_ZERO: u8 = 0x83;
pub const OP_LE_ZERO: u8 = 0x84;
pub const OP_LT_ZERO: u8 = 0x85;
pub const OP_OR_VERIFY: u8 = 0x64;
pub const OP_OR: u8 = 0x65;
pub const OP_CHECK_SIG: u8 = 0xac;
pub const OP_CHECK_SIG_VERIFY: u8 = 0xad;
pub const OP_HASH_BLAKE256: u8 = 0xb0;
pub const OP_HASH_SHA256: u8 = 0xb1;
pub const OP_HASH_SHA3: u8 = 0xb2;
pub const OP_RETURN: u8 = 0x60;
pub const OP_IF_THEN: u8 = 0x61;
pub const OP_ELSE: u8 = 0x62;
pub const OP_END_IF: u8 = 0x63;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Opcode {
CheckHeightVerify(u64),
CheckHeight(u64),
CompareHeightVerify,
CompareHeight,
Nop,
PushZero,
PushOne,
PushHash(Box<HashValue>),
PushInt(i64),
PushPubKey(Box<RistrettoPublicKey>),
Drop,
Dup,
RevRot,
GeZero,
GtZero,
LeZero,
LtZero,
Add,
Sub,
Equal,
EqualVerify,
Or(u8),
OrVerify(u8),
HashBlake256,
HashSha256,
HashSha3,
CheckSig(Box<Message>),
CheckSigVerify(Box<Message>),
Return,
IfThen,
Else,
EndIf,
}
impl Opcode {
pub fn parse(bytes: &[u8]) -> Result<Vec<Opcode>, ScriptError> {
let mut script = Vec::with_capacity(512);
let mut bytes_copy = bytes;
while !bytes_copy.is_empty() {
let (opcode, bytes_left) = Opcode::read_next(bytes_copy)?;
script.push(opcode);
bytes_copy = bytes_left;
}
Ok(script)
}
fn read_next(bytes: &[u8]) -> Result<(Opcode, &[u8]), ScriptError> {
let code = bytes.get(0).ok_or(ScriptError::InvalidOpcode)?;
use Opcode::*;
match *code {
OP_CHECK_HEIGHT_VERIFY => {
if bytes.len() < 9 {
return Err(ScriptError::InvalidData);
}
let height = slice_to_u64(&bytes[1..9]);
Ok((CheckHeightVerify(height), &bytes[9..]))
},
OP_CHECK_HEIGHT => {
if bytes.len() < 9 {
return Err(ScriptError::InvalidData);
}
let height = slice_to_u64(&bytes[1..9]);
Ok((CheckHeight(height), &bytes[9..]))
},
OP_COMPARE_HEIGHT_VERIFY => Ok((CompareHeightVerify, &bytes[1..])),
OP_COMPARE_HEIGHT => Ok((CompareHeight, &bytes[1..])),
OP_NOP => Ok((Nop, &bytes[1..])),
OP_PUSH_ZERO => Ok((PushZero, &bytes[1..])),
OP_PUSH_ONE => Ok((PushOne, &bytes[1..])),
OP_PUSH_HASH => {
if bytes.len() < 33 {
return Err(ScriptError::InvalidData);
}
let hash = slice_to_boxed_hash(&bytes[1..33]);
Ok((PushHash(hash), &bytes[33..]))
},
OP_PUSH_INT => {
if bytes.len() < 9 {
return Err(ScriptError::InvalidData);
}
let n = slice_to_i64(&bytes[1..9]);
Ok((PushInt(n), &bytes[9..]))
},
OP_PUSH_PUBKEY => {
if bytes.len() < 33 {
return Err(ScriptError::InvalidData);
}
let p = RistrettoPublicKey::from_bytes(&bytes[1..33])?;
Ok((PushPubKey(Box::new(p)), &bytes[33..]))
},
OP_DROP => Ok((Drop, &bytes[1..])),
OP_DUP => Ok((Dup, &bytes[1..])),
OP_REV_ROT => Ok((RevRot, &bytes[1..])),
OP_GE_ZERO => Ok((GeZero, &bytes[1..])),
OP_GT_ZERO => Ok((GtZero, &bytes[1..])),
OP_LE_ZERO => Ok((LeZero, &bytes[1..])),
OP_LT_ZERO => Ok((LtZero, &bytes[1..])),
OP_ADD => Ok((Add, &bytes[1..])),
OP_SUB => Ok((Sub, &bytes[1..])),
OP_EQUAL => Ok((Equal, &bytes[1..])),
OP_EQUAL_VERIFY => Ok((EqualVerify, &bytes[1..])),
OP_OR => {
if bytes.len() < 2 {
return Err(ScriptError::InvalidData);
}
let n = &bytes[1];
Ok((Or(*n), &bytes[2..]))
},
OP_OR_VERIFY => {
if bytes.len() < 2 {
return Err(ScriptError::InvalidData);
}
let n = &bytes[1];
Ok((OrVerify(*n), &bytes[2..]))
},
OP_HASH_BLAKE256 => Ok((HashBlake256, &bytes[1..])),
OP_HASH_SHA256 => Ok((HashSha256, &bytes[1..])),
OP_HASH_SHA3 => Ok((HashSha3, &bytes[1..])),
OP_CHECK_SIG => {
if bytes.len() < 33 {
return Err(ScriptError::InvalidData);
}
let msg = slice_to_boxed_message(&bytes[1..33]);
Ok((CheckSig(msg), &bytes[33..]))
},
OP_CHECK_SIG_VERIFY => {
if bytes.len() < 33 {
return Err(ScriptError::InvalidData);
}
let msg = slice_to_boxed_message(&bytes[1..33]);
Ok((CheckSigVerify(msg), &bytes[33..]))
},
OP_RETURN => Ok((Return, &bytes[1..])),
OP_IF_THEN => Ok((IfThen, &bytes[1..])),
OP_ELSE => Ok((Else, &bytes[1..])),
OP_END_IF => Ok((EndIf, &bytes[1..])),
_ => Err(ScriptError::InvalidOpcode),
}
}
pub fn to_bytes<'a>(&self, array: &'a mut Vec<u8>) -> &'a [u8] {
let n = array.len();
use Opcode::*;
match self {
CheckHeightVerify(height) => {
array.push(OP_CHECK_HEIGHT_VERIFY);
array.extend_from_slice(&height.to_le_bytes());
},
CheckHeight(height) => {
array.push(OP_CHECK_HEIGHT);
array.extend_from_slice(&height.to_le_bytes());
},
CompareHeightVerify => array.push(OP_COMPARE_HEIGHT_VERIFY),
CompareHeight => array.push(OP_COMPARE_HEIGHT),
Nop => array.push(OP_NOP),
PushZero => array.push(OP_PUSH_ZERO),
PushOne => array.push(OP_PUSH_ONE),
PushHash(h) => {
array.push(OP_PUSH_HASH);
array.extend_from_slice(h.deref());
},
PushInt(n) => {
array.push(OP_PUSH_INT);
array.extend_from_slice(&n.to_le_bytes());
},
PushPubKey(p) => {
array.push(OP_PUSH_PUBKEY);
array.extend_from_slice(p.deref().as_bytes());
},
Drop => array.push(OP_DROP),
Dup => array.push(OP_DUP),
RevRot => array.push(OP_REV_ROT),
GeZero => array.push(OP_GE_ZERO),
GtZero => array.push(OP_GT_ZERO),
LeZero => array.push(OP_LE_ZERO),
LtZero => array.push(OP_LT_ZERO),
Add => array.push(OP_ADD),
Sub => array.push(OP_SUB),
Equal => array.push(OP_EQUAL),
EqualVerify => array.push(OP_EQUAL_VERIFY),
Or(n) => {
array.push(OP_OR);
array.push(*n);
},
OrVerify(n) => {
array.push(OP_OR_VERIFY);
array.push(*n);
},
HashBlake256 => array.push(OP_HASH_BLAKE256),
HashSha256 => array.push(OP_HASH_SHA256),
HashSha3 => array.push(OP_HASH_SHA3),
CheckSig(msg) => {
array.push(OP_CHECK_SIG);
array.extend_from_slice(msg.deref());
},
CheckSigVerify(msg) => {
array.push(OP_CHECK_SIG_VERIFY);
array.extend_from_slice(msg.deref());
},
Return => array.push(OP_RETURN),
IfThen => array.push(OP_IF_THEN),
Else => array.push(OP_ELSE),
EndIf => array.push(OP_END_IF),
};
&array[n..]
}
}
impl fmt::Display for Opcode {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use Opcode::*;
match self {
CheckHeightVerify(height) => fmt.write_str(&format!("CheckHeightVerify({})", *height)),
CheckHeight(height) => fmt.write_str(&format!("CheckHeight({})", *height)),
CompareHeightVerify => fmt.write_str("CompareHeightVerify"),
CompareHeight => fmt.write_str("CompareHeight"),
Nop => fmt.write_str("Nop"),
PushZero => fmt.write_str("PushZero"),
PushOne => fmt.write_str("PushOne"),
PushHash(h) => fmt.write_str(&format!("PushHash({})", (*h).to_hex())),
PushInt(n) => fmt.write_str(&format!("PushInt({})", *n)),
PushPubKey(h) => fmt.write_str(&format!("PushPubKey({})", (*h).to_hex())),
Drop => fmt.write_str("Drop"),
Dup => fmt.write_str("Dup"),
RevRot => fmt.write_str("RevRot"),
GeZero => fmt.write_str("GeZero"),
GtZero => fmt.write_str("GtZero"),
LeZero => fmt.write_str("LeZero"),
LtZero => fmt.write_str("LtZero"),
Add => fmt.write_str("Add"),
Sub => fmt.write_str("Sub"),
Equal => fmt.write_str("Equal"),
EqualVerify => fmt.write_str("EqualVerify"),
Or(n) => fmt.write_str(&format!("Or({})", *n)),
OrVerify(n) => fmt.write_str(&format!("OrVerify({})", *n)),
HashBlake256 => fmt.write_str("HashBlake256"),
HashSha256 => fmt.write_str("HashSha256"),
HashSha3 => fmt.write_str("HashSha3"),
CheckSig(msg) => fmt.write_str(&format!("CheckSig({})", (*msg).to_hex())),
CheckSigVerify(msg) => fmt.write_str(&format!("CheckSigVerify({})", (*msg).to_hex())),
Return => fmt.write_str("Return"),
IfThen => fmt.write_str("IfThen"),
Else => fmt.write_str("Else"),
EndIf => fmt.write_str("EndIf"),
}
}
}
#[cfg(test)]
mod test {
use crate::script::{op_codes::*, Opcode, Opcode::*, ScriptError};
#[test]
fn empty_script() {
assert_eq!(Opcode::parse(&[]).unwrap(), Vec::new())
}
#[test]
fn parse() {
let script = [0x60u8, 0x71, 0x00];
let err = Opcode::parse(&script).unwrap_err();
assert!(matches!(err, ScriptError::InvalidOpcode));
let script = [0x60u8, 0x71];
let opcodes = Opcode::parse(&script).unwrap();
let code = opcodes.first().unwrap();
assert_eq!(code, &Return);
let code = opcodes.get(1).unwrap();
assert_eq!(code, &Dup);
let err = Opcode::parse(&[0x7a]).unwrap_err();
assert!(matches!(err, ScriptError::InvalidData));
}
#[test]
fn push_hash() {
let (code, b) = Opcode::read_next(b"\x7a/thirty-two~character~hash~val./").unwrap();
assert!(matches!(code, PushHash(v) if &*v == b"/thirty-two~character~hash~val./"));
assert!(b.is_empty());
}
#[test]
fn slice_to_u64_tests() {
let val = slice_to_u64(&[0, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(val, 0);
let val = slice_to_u64(&[63, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(val, 63);
let val = slice_to_u64(&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F]);
assert_eq!(val, 9_223_372_036_854_775_807);
}
#[test]
fn slice_to_i64_tests() {
let val = slice_to_i64(&[0, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(val, 0);
let val = slice_to_i64(&[63, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(val, 63);
let val = slice_to_i64(&[63, 0, 0, 0, 0, 0, 0, 128]);
assert_eq!(val, -9_223_372_036_854_775_745);
let val = slice_to_i64(&[0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80]);
assert_eq!(val, -9_151_314_442_816_848_128);
}
#[test]
fn check_height() {
fn test_check_height(op: Opcode, val: u8, display: &str) {
assert!(matches!(
Opcode::read_next(&[val, 1, 2, 3]),
Err(ScriptError::InvalidData)
));
let s = &[val, 63, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3];
let (opcode, rem) = Opcode::read_next(s).unwrap();
assert_eq!(opcode, op);
assert_eq!(rem, &[1, 2, 3]);
let mut arr = vec![1, 2, 3];
op.to_bytes(&mut arr);
assert_eq!(&arr, &[1, 2, 3, val, 63, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(format!("{}", op).as_str(), display);
}
test_check_height(Opcode::CheckHeight(63), 0x67, "CheckHeight(63)");
test_check_height(Opcode::CheckHeightVerify(63), 0x66, "CheckHeightVerify(63)");
}
#[test]
fn push_int() {
assert!(matches!(Opcode::read_next(b"\x7dshort"), Err(ScriptError::InvalidData)));
let s = &[OP_PUSH_INT, 1, 1, 0, 0, 0, 0, 0, 0];
let (opcode, rem) = Opcode::read_next(s).unwrap();
assert!(matches!(opcode, Opcode::PushInt(257)));
assert!(rem.is_empty());
let op = Opcode::PushInt(257);
let mut arr = vec![];
op.to_bytes(&mut arr);
assert_eq!(&arr, &[OP_PUSH_INT, 1, 1, 0, 0, 0, 0, 0, 0]);
assert_eq!(format!("{}", op).as_str(), "PushInt(257)");
}
#[test]
fn push_pubkey() {
assert!(matches!(
Opcode::read_next(b"\x7eshort_needs_33_bytes"),
Err(ScriptError::InvalidData)
));
let key =
RistrettoPublicKey::from_hex("6c9cb4d3e57351462122310fa22c90b1e6dfb528d64615363d1261a75da3e401").unwrap();
let s = &[
OP_PUSH_PUBKEY,
108,
156,
180,
211,
229,
115,
81,
70,
33,
34,
49,
15,
162,
44,
144,
177,
230,
223,
181,
40,
214,
70,
21,
54,
61,
18,
97,
167,
93,
163,
228,
1,
];
let op = Opcode::PushPubKey(Box::new(key.clone()));
let (opcode, rem) = Opcode::read_next(s).unwrap();
assert_eq!(opcode, op);
assert!(rem.is_empty());
let mut arr = vec![];
op.to_bytes(&mut arr);
assert_eq!(&arr, s);
assert_eq!(
format!("{}", op).as_str(),
"PushPubKey(6c9cb4d3e57351462122310fa22c90b1e6dfb528d64615363d1261a75da3e401)"
);
}
#[test]
fn or() {
fn test_or(op: Opcode, val: u8, display: &str) {
assert!(matches!(Opcode::read_next(&[val]), Err(ScriptError::InvalidData)));
let s = &[val, 5, 83];
let (opcode, rem) = Opcode::read_next(s).unwrap();
assert_eq!(opcode, op);
assert_eq!(rem, &[83]);
let mut arr = vec![];
op.to_bytes(&mut arr);
assert_eq!(&arr, &[val, 5]);
assert_eq!(format!("{}", op).as_str(), display);
}
test_or(Opcode::Or(5), OP_OR, "Or(5)");
test_or(Opcode::OrVerify(5), OP_OR_VERIFY, "OrVerify(5)");
}
#[test]
fn check_sig() {
fn test_checksig(op: Opcode, val: u8, display: &str) {
assert!(matches!(Opcode::read_next(&[val]), Err(ScriptError::InvalidData)));
let msg = &[
val, 108, 156, 180, 211, 229, 115, 81, 70, 33, 34, 49, 15, 162, 44, 144, 177, 230, 223, 181, 40, 214,
70, 21, 54, 61, 18, 97, 167, 93, 163, 228, 1,
];
let (opcode, rem) = Opcode::read_next(msg).unwrap();
assert_eq!(opcode, op);
assert!(rem.is_empty());
let mut arr = vec![];
op.to_bytes(&mut arr);
assert_eq!(arr, msg);
assert_eq!(format!("{}", op).as_str(), display);
}
let msg = &[
108, 156, 180, 211, 229, 115, 81, 70, 33, 34, 49, 15, 162, 44, 144, 177, 230, 223, 181, 40, 214, 70, 21,
54, 61, 18, 97, 167, 93, 163, 228, 1,
];
test_checksig(
Opcode::CheckSig(Box::new(msg.clone())),
OP_CHECK_SIG,
"CheckSig(6c9cb4d3e57351462122310fa22c90b1e6dfb528d64615363d1261a75da3e401)",
);
test_checksig(
Opcode::CheckSigVerify(Box::new(msg.clone())),
OP_CHECK_SIG_VERIFY,
"CheckSigVerify(6c9cb4d3e57351462122310fa22c90b1e6dfb528d64615363d1261a75da3e401)",
);
}
#[test]
fn deserialise_no_param_opcodes() {
fn test_opcode(code: u8, expected: Opcode) {
let s = &[code, 1, 2, 3];
let (opcode, rem) = Opcode::read_next(s).unwrap();
assert_eq!(opcode, expected);
assert_eq!(rem, &[1, 2, 3]);
}
test_opcode(OP_COMPARE_HEIGHT_VERIFY, Opcode::CompareHeightVerify);
test_opcode(OP_COMPARE_HEIGHT, Opcode::CompareHeight);
test_opcode(OP_NOP, Opcode::Nop);
test_opcode(OP_PUSH_ZERO, Opcode::PushZero);
test_opcode(OP_PUSH_ONE, Opcode::PushOne);
test_opcode(OP_DROP, Opcode::Drop);
test_opcode(OP_DUP, Opcode::Dup);
test_opcode(OP_REV_ROT, Opcode::RevRot);
test_opcode(OP_GE_ZERO, Opcode::GeZero);
test_opcode(OP_GT_ZERO, Opcode::GtZero);
test_opcode(OP_LE_ZERO, Opcode::LeZero);
test_opcode(OP_LT_ZERO, Opcode::LtZero);
test_opcode(OP_EQUAL, Opcode::Equal);
test_opcode(OP_EQUAL_VERIFY, Opcode::EqualVerify);
test_opcode(OP_HASH_SHA3, Opcode::HashSha3);
test_opcode(OP_HASH_BLAKE256, Opcode::HashBlake256);
test_opcode(OP_HASH_SHA256, Opcode::HashSha256);
test_opcode(OP_IF_THEN, Opcode::IfThen);
test_opcode(OP_ELSE, Opcode::Else);
test_opcode(OP_END_IF, Opcode::EndIf);
test_opcode(OP_ADD, Opcode::Add);
test_opcode(OP_SUB, Opcode::Sub);
test_opcode(OP_RETURN, Opcode::Return);
}
#[test]
fn serialise_no_param_opcodes() {
fn test_opcode(val: u8, opcode: Opcode) {
let mut arr = vec![];
assert_eq!(opcode.to_bytes(&mut arr), &[val]);
}
test_opcode(OP_COMPARE_HEIGHT_VERIFY, Opcode::CompareHeightVerify);
test_opcode(OP_COMPARE_HEIGHT, Opcode::CompareHeight);
test_opcode(OP_NOP, Opcode::Nop);
test_opcode(OP_PUSH_ZERO, Opcode::PushZero);
test_opcode(OP_PUSH_ONE, Opcode::PushOne);
test_opcode(OP_DROP, Opcode::Drop);
test_opcode(OP_DUP, Opcode::Dup);
test_opcode(OP_REV_ROT, Opcode::RevRot);
test_opcode(OP_GE_ZERO, Opcode::GeZero);
test_opcode(OP_GT_ZERO, Opcode::GtZero);
test_opcode(OP_LE_ZERO, Opcode::LeZero);
test_opcode(OP_LT_ZERO, Opcode::LtZero);
test_opcode(OP_EQUAL, Opcode::Equal);
test_opcode(OP_EQUAL_VERIFY, Opcode::EqualVerify);
test_opcode(OP_HASH_SHA3, Opcode::HashSha3);
test_opcode(OP_HASH_BLAKE256, Opcode::HashBlake256);
test_opcode(OP_HASH_SHA256, Opcode::HashSha256);
test_opcode(OP_IF_THEN, Opcode::IfThen);
test_opcode(OP_ELSE, Opcode::Else);
test_opcode(OP_END_IF, Opcode::EndIf);
test_opcode(OP_ADD, Opcode::Add);
test_opcode(OP_SUB, Opcode::Sub);
test_opcode(OP_RETURN, Opcode::Return);
}
#[test]
fn display() {
fn test_opcode(opcode: Opcode, expected: &str) {
let s = format!("{}", opcode);
assert_eq!(s.as_str(), expected);
}
test_opcode(Opcode::CompareHeightVerify, "CompareHeightVerify");
test_opcode(Opcode::CompareHeight, "CompareHeight");
test_opcode(Opcode::Nop, "Nop");
test_opcode(Opcode::PushZero, "PushZero");
test_opcode(Opcode::PushOne, "PushOne");
test_opcode(Opcode::Drop, "Drop");
test_opcode(Opcode::Dup, "Dup");
test_opcode(Opcode::RevRot, "RevRot");
test_opcode(Opcode::GeZero, "GeZero");
test_opcode(Opcode::GtZero, "GtZero");
test_opcode(Opcode::LeZero, "LeZero");
test_opcode(Opcode::LtZero, "LtZero");
test_opcode(Opcode::Equal, "Equal");
test_opcode(Opcode::EqualVerify, "EqualVerify");
test_opcode(Opcode::HashSha3, "HashSha3");
test_opcode(Opcode::HashBlake256, "HashBlake256");
test_opcode(Opcode::HashSha256, "HashSha256");
test_opcode(Opcode::IfThen, "IfThen");
test_opcode(Opcode::Else, "Else");
test_opcode(Opcode::EndIf, "EndIf");
test_opcode(Opcode::Add, "Add");
test_opcode(Opcode::Sub, "Sub");
test_opcode(Opcode::Return, "Return");
}
}