#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "std", doc = include_str!("../README.md"))]
#![warn(missing_docs)]
#![warn(unsafe_code)]
#![deny(unused_crate_dependencies)]
mod args;
mod instruction_result;
#[macro_use]
pub mod macros;
pub mod op;
mod pack;
mod panic_reason;
mod unpack;
#[cfg(test)]
mod encoding_tests;
#[doc(no_inline)]
pub use args::{GMArgs, GTFArgs};
pub use fuel_types::{RegisterId, Word};
pub use instruction_result::InstructionResult;
pub use panic_reason::PanicReason;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RegId(u8);
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Imm06(u8);
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Imm12(u16);
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Imm18(u32);
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Imm24(u32);
pub type RawInstruction = u32;
#[derive(Debug, Eq, PartialEq)]
pub struct InvalidOpcode;
bitflags::bitflags! {
pub struct Flags: Word {
const UNSAFEMATH = 0x01;
const WRAPPING = 0x02;
}
}
pub trait CheckRegId {
fn check(self) -> RegId;
}
impl CheckRegId for RegId {
fn check(self) -> RegId {
self
}
}
impl CheckRegId for u8 {
fn check(self) -> RegId {
RegId::new_checked(self).expect("CheckRegId was given invalid RegId")
}
}
impl_instructions! {
"Adds two registers."
0x10 ADD add [RegId RegId RegId]
"Bitwise ANDs two registers."
0x11 AND and [RegId RegId RegId]
"Divides two registers."
0x12 DIV div [RegId RegId RegId]
"Compares two registers for equality."
0x13 EQ eq [RegId RegId RegId]
"Raises one register to the power of another."
0x14 EXP exp [RegId RegId RegId]
"Compares two registers for greater-than."
0x15 GT gt [RegId RegId RegId]
"Compares two registers for less-than."
0x16 LT lt [RegId RegId RegId]
"The integer logarithm of a register."
0x17 MLOG mlog [RegId RegId RegId]
"The integer root of a register."
0x18 MROO mroo [RegId RegId RegId]
"Modulo remainder of two registers."
0x19 MOD mod_ [RegId RegId RegId]
"Copy from one register to another."
0x1A MOVE move_ [RegId RegId]
"Multiplies two registers."
0x1B MUL mul [RegId RegId RegId]
"Bitwise NOT a register."
0x1C NOT not [RegId RegId]
"Bitwise ORs two registers."
0x1D OR or [RegId RegId RegId]
"Left shifts a register by a register."
0x1E SLL sll [RegId RegId RegId]
"Right shifts a register by a register."
0x1F SRL srl [RegId RegId RegId]
"Subtracts two registers."
0x20 SUB sub [RegId RegId RegId]
"Bitwise XORs two registers."
0x21 XOR xor [RegId RegId RegId]
"Return from context."
0x24 RET ret [RegId]
"Return from context with data."
0x25 RETD retd [RegId RegId]
"Allocate a number of bytes from the heap."
0x26 ALOC aloc [RegId]
"Clear a variable number of bytes in memory."
0x27 MCL mcl [RegId RegId]
"Copy a variable number of bytes in memory."
0x28 MCP mcp [RegId RegId RegId]
"Compare bytes in memory."
0x29 MEQ meq [RegId RegId RegId RegId]
"Get block header hash for height."
0x2A BHSH bhsh [RegId RegId]
"Get current block height."
0x2B BHEI bhei [RegId]
"Burn coins of the current contract's asset ID."
0x2C BURN burn [RegId]
"Call a contract."
0x2D CALL call [RegId RegId RegId RegId]
"Copy contract code for a contract."
0x2E CCP ccp [RegId RegId RegId RegId]
"Get code root of a contract."
0x2F CROO croo [RegId RegId]
"Get code size of a contract."
0x30 CSIZ csiz [RegId RegId]
"Get current block proposer's address."
0x31 CB cb [RegId]
"Load a contract's code as executable."
0x32 LDC ldc [RegId RegId RegId]
"Log an event."
0x33 LOG log [RegId RegId RegId RegId]
"Log data."
0x34 LOGD logd [RegId RegId RegId RegId]
"Mint coins of the current contract's asset ID."
0x35 MINT mint [RegId]
"Halt execution, reverting state changes and returning a value."
0x36 RVRT rvrt [RegId]
"Clear a series of slots from contract storage."
0x37 SCWQ scwq [RegId RegId RegId]
"Load a word from contract storage."
0x38 SRW srw [RegId RegId RegId]
"Load a series of 32 byte slots from contract storage."
0x39 SRWQ srwq [RegId RegId RegId RegId]
"Store a word in contract storage."
0x3A SWW sww [RegId RegId RegId]
"Store a series of 32 byte slots in contract storage."
0x3B SWWQ swwq [RegId RegId RegId RegId]
"Transfer coins to a contract unconditionally."
0x3C TR tr [RegId RegId RegId]
"Transfer coins to a variable output."
0x3D TRO tro [RegId RegId RegId RegId]
"The 64-byte public key (x, y) recovered from 64-byte signature on 32-byte message."
0x3E ECR ecr [RegId RegId RegId]
"The keccak-256 hash of a slice."
0x3F K256 k256 [RegId RegId RegId]
"The SHA-2-256 hash of a slice."
0x40 S256 s256 [RegId RegId RegId]
"Get timestamp of block at given height."
0x41 TIME time [RegId RegId]
"Performs no operation."
0x47 NOOP noop []
"Set flag register to a register."
0x48 FLAG flag [RegId]
"Get the balance of contract of an asset ID."
0x49 BAL bal [RegId RegId RegId]
"Dynamic jump."
0x4A JMP jmp [RegId]
"Conditional dynamic jump."
0x4B JNE jne [RegId RegId RegId]
"Send a message to recipient address with call abi, coins, and output."
0x4C SMO smo [RegId RegId RegId RegId]
"Adds a register and an immediate value."
0x50 ADDI addi [RegId RegId Imm12]
"Bitwise ANDs a register and an immediate value."
0x51 ANDI andi [RegId RegId Imm12]
"Divides a register and an immediate value."
0x52 DIVI divi [RegId RegId Imm12]
"Raises one register to the power of an immediate value."
0x53 EXPI expi [RegId RegId Imm12]
"Modulo remainder of a register and an immediate value."
0x54 MODI modi [RegId RegId Imm12]
"Multiplies a register and an immediate value."
0x55 MULI muli [RegId RegId Imm12]
"Bitwise ORs a register and an immediate value."
0x56 ORI ori [RegId RegId Imm12]
"Left shifts a register by an immediate value."
0x57 SLLI slli [RegId RegId Imm12]
"Right shifts a register by an immediate value."
0x58 SRLI srli [RegId RegId Imm12]
"Subtracts a register and an immediate value."
0x59 SUBI subi [RegId RegId Imm12]
"Bitwise XORs a register and an immediate value."
0x5A XORI xori [RegId RegId Imm12]
"Conditional jump."
0x5B JNEI jnei [RegId RegId Imm12]
"A byte is loaded from the specified address offset by an immediate value."
0x5C LB lb [RegId RegId Imm12]
"A word is loaded from the specified address offset by an immediate value."
0x5D LW lw [RegId RegId Imm12]
"Write the least significant byte of a register to memory."
0x5E SB sb [RegId RegId Imm12]
"Write a register to memory."
0x5F SW sw [RegId RegId Imm12]
"Copy an immediate number of bytes in memory."
0x60 MCPI mcpi [RegId RegId Imm12]
"Get transaction fields."
0x61 GTF gtf [RegId RegId Imm12]
"Clear an immediate number of bytes in memory."
0x70 MCLI mcli [RegId Imm18]
"Get metadata from memory."
0x71 GM gm [RegId Imm18]
"Copy immediate value into a register"
0x72 MOVI movi [RegId Imm18]
"Conditional jump against zero."
0x73 JNZI jnzi [RegId Imm18]
"Unconditional dynamic relative jump forwards, with a constant offset."
0x74 JMPF jmpf [RegId Imm18]
"Unconditional dynamic relative jump backwards, with a constant offset."
0x75 JMPB jmpb [RegId Imm18]
"Dynamic relative jump forwards, conditional against zero, with a constant offset."
0x76 JNZF jnzf [RegId RegId Imm12]
"Dynamic relative jump backwards, conditional against zero, with a constant offset."
0x77 JNZB jnzb [RegId RegId Imm12]
"Dynamic relative jump forwards, conditional on comparsion, with a constant offset."
0x78 JNEF jnef [RegId RegId RegId Imm06]
"Dynamic relative jump backwards, conditional on comparsion, with a constant offset."
0x79 JNEB jneb [RegId RegId RegId Imm06]
"Jump."
0x90 JI ji [Imm24]
"Extend the current call frame's stack by an immediate value."
0x91 CFEI cfei [Imm24]
"Shrink the current call frame's stack by an immediate value."
0x92 CFSI cfsi [Imm24]
"Extend the current call frame's stack"
0x93 CFE cfe [RegId]
"Shrink the current call frame's stack"
0x94 CFS cfs [RegId]
}
impl Instruction {
pub const SIZE: usize = core::mem::size_of::<Instruction>();
pub fn to_bytes(self) -> [u8; 4] {
self.into()
}
}
impl RegId {
pub const ZERO: Self = Self(0x00);
pub const ONE: Self = Self(0x01);
pub const OF: Self = Self(0x02);
pub const PC: Self = Self(0x03);
pub const SSP: Self = Self(0x04);
pub const SP: Self = Self(0x05);
pub const FP: Self = Self(0x06);
pub const HP: Self = Self(0x07);
pub const ERR: Self = Self(0x08);
pub const GGAS: Self = Self(0x09);
pub const CGAS: Self = Self(0x0A);
pub const BAL: Self = Self(0x0B);
pub const IS: Self = Self(0x0C);
pub const RET: Self = Self(0x0D);
pub const RETL: Self = Self(0x0E);
pub const FLAG: Self = Self(0x0F);
pub const WRITABLE: Self = Self(0x10);
pub const fn new(u: u8) -> Self {
Self(u & 0b_0011_1111)
}
pub fn new_checked(u: u8) -> Option<Self> {
let r = Self::new(u);
(r.0 == u).then_some(r)
}
pub const fn to_u8(self) -> u8 {
self.0
}
}
impl Imm06 {
pub const MAX: Self = Self(0b_0011_1111);
pub const fn new(u: u8) -> Self {
Self(u & Self::MAX.0)
}
pub fn new_checked(u: u8) -> Option<Self> {
let imm = Self::new(u);
(imm.0 == u).then_some(imm)
}
pub const fn to_u8(self) -> u8 {
self.0
}
}
impl Imm12 {
pub const MAX: Self = Self(0b_0000_1111_1111_1111);
pub const fn new(u: u16) -> Self {
Self(u & Self::MAX.0)
}
pub fn new_checked(u: u16) -> Option<Self> {
let imm = Self::new(u);
(imm.0 == u).then_some(imm)
}
pub const fn to_u16(self) -> u16 {
self.0
}
}
impl Imm18 {
pub const MAX: Self = Self(0b_0000_0000_0000_0011_1111_1111_1111_1111);
pub const fn new(u: u32) -> Self {
Self(u & Self::MAX.0)
}
pub fn new_checked(u: u32) -> Option<Self> {
let imm = Self::new(u);
(imm.0 == u).then_some(imm)
}
pub const fn to_u32(self) -> u32 {
self.0
}
}
impl Imm24 {
pub const MAX: Self = Self(0b_0000_0000_1111_1111_1111_1111_1111_1111);
pub const fn new(u: u32) -> Self {
Self(u & Self::MAX.0)
}
pub fn new_checked(u: u32) -> Option<Self> {
let imm = Self::new(u);
(imm.0 == u).then_some(imm)
}
pub const fn to_u32(self) -> u32 {
self.0
}
}
impl Opcode {
#[allow(clippy::match_like_matches_macro)]
pub fn is_predicate_allowed(&self) -> bool {
use Opcode::*;
match self {
ADD | AND | DIV | EQ | EXP | GT | LT | MLOG | MROO | MOD | MOVE | MUL | NOT | OR | SLL | SRL | SUB
| XOR | RET | ALOC | MCL | MCP | MEQ | ECR | K256 | S256 | NOOP | FLAG | ADDI | ANDI | DIVI | EXPI
| MODI | MULI | ORI | SLLI | SRLI | SUBI | XORI | JNEI | LB | LW | SB | SW | MCPI | MCLI | GM | MOVI
| JNZI | JI | JMP | JNE | JMPF | JMPB | JNZF | JNZB | JNEF | JNEB | CFEI | CFSI | CFE | CFS | GTF => true,
_ => false,
}
}
}
impl From<u8> for RegId {
fn from(u: u8) -> Self {
RegId::new(u)
}
}
impl From<u8> for Imm06 {
fn from(u: u8) -> Self {
Imm06::new(u)
}
}
impl From<u16> for Imm12 {
fn from(u: u16) -> Self {
Imm12::new(u)
}
}
impl From<u32> for Imm18 {
fn from(u: u32) -> Self {
Imm18::new(u)
}
}
impl From<u32> for Imm24 {
fn from(u: u32) -> Self {
Imm24::new(u)
}
}
impl From<RegId> for u8 {
fn from(RegId(u): RegId) -> Self {
u
}
}
impl From<Imm06> for u8 {
fn from(Imm06(u): Imm06) -> Self {
u
}
}
impl From<Imm12> for u16 {
fn from(Imm12(u): Imm12) -> Self {
u
}
}
impl From<Imm18> for u32 {
fn from(Imm18(u): Imm18) -> Self {
u
}
}
impl From<Imm24> for u32 {
fn from(Imm24(u): Imm24) -> Self {
u
}
}
impl From<RegId> for usize {
fn from(r: RegId) -> usize {
u8::from(r).into()
}
}
impl From<Imm06> for u16 {
fn from(imm: Imm06) -> Self {
u8::from(imm).into()
}
}
impl From<Imm06> for u32 {
fn from(imm: Imm06) -> Self {
u8::from(imm).into()
}
}
impl From<Imm06> for u64 {
fn from(imm: Imm06) -> Self {
u8::from(imm).into()
}
}
impl From<Imm06> for u128 {
fn from(imm: Imm06) -> Self {
u8::from(imm).into()
}
}
impl From<Imm12> for u32 {
fn from(imm: Imm12) -> Self {
u16::from(imm).into()
}
}
impl From<Imm12> for u64 {
fn from(imm: Imm12) -> Self {
u16::from(imm).into()
}
}
impl From<Imm12> for u128 {
fn from(imm: Imm12) -> Self {
u16::from(imm).into()
}
}
impl From<Imm18> for u64 {
fn from(imm: Imm18) -> Self {
u32::from(imm).into()
}
}
impl From<Imm18> for u128 {
fn from(imm: Imm18) -> Self {
u32::from(imm).into()
}
}
impl From<Imm24> for u64 {
fn from(imm: Imm24) -> Self {
u32::from(imm).into()
}
}
impl From<Imm24> for u128 {
fn from(imm: Imm24) -> Self {
u32::from(imm).into()
}
}
impl From<Opcode> for u8 {
fn from(op: Opcode) -> Self {
op as u8
}
}
impl From<Instruction> for RawInstruction {
fn from(inst: Instruction) -> Self {
RawInstruction::from_be_bytes(inst.into())
}
}
impl core::convert::TryFrom<RawInstruction> for Instruction {
type Error = InvalidOpcode;
fn try_from(u: RawInstruction) -> Result<Self, Self::Error> {
Self::try_from(u.to_be_bytes())
}
}
impl<T> core::ops::Index<RegId> for [T]
where
[T]: core::ops::Index<usize, Output = T>,
{
type Output = T;
fn index(&self, ix: RegId) -> &Self::Output {
&self[usize::from(ix)]
}
}
impl<T> core::ops::IndexMut<RegId> for [T]
where
[T]: core::ops::IndexMut<usize, Output = T>,
{
fn index_mut(&mut self, ix: RegId) -> &mut Self::Output {
&mut self[usize::from(ix)]
}
}
#[cfg(feature = "std")]
impl core::iter::FromIterator<Instruction> for Vec<u8> {
fn from_iter<I: IntoIterator<Item = Instruction>>(iter: I) -> Self {
iter.into_iter().flat_map(Instruction::to_bytes).collect()
}
}
#[cfg(feature = "std")]
impl core::iter::FromIterator<Instruction> for Vec<u32> {
fn from_iter<I: IntoIterator<Item = Instruction>>(iter: I) -> Self {
iter.into_iter().map(u32::from).collect()
}
}
pub fn raw_instructions_from_word(word: Word) -> [RawInstruction; 2] {
let hi = (word >> 32) as RawInstruction;
let lo = word as RawInstruction;
[hi, lo]
}
pub fn from_bytes<I>(bs: I) -> impl Iterator<Item = Result<Instruction, InvalidOpcode>>
where
I: IntoIterator<Item = u8>,
{
let mut iter = bs.into_iter();
core::iter::from_fn(move || {
let a = iter.next()?;
let b = iter.next()?;
let c = iter.next()?;
let d = iter.next()?;
Some(Instruction::try_from([a, b, c, d]))
})
}
pub fn from_u32s<I>(us: I) -> impl Iterator<Item = Result<Instruction, InvalidOpcode>>
where
I: IntoIterator<Item = u32>,
{
us.into_iter().map(Instruction::try_from)
}
fn check_imm06(u: u8) -> Imm06 {
Imm06::new_checked(u).unwrap_or_else(|| panic!("Value `{u}` out of range for 6-bit immediate"))
}
fn check_imm12(u: u16) -> Imm12 {
Imm12::new_checked(u).unwrap_or_else(|| panic!("Value `{u}` out of range for 12-bit immediate"))
}
fn check_imm18(u: u32) -> Imm18 {
Imm18::new_checked(u).unwrap_or_else(|| panic!("Value `{u}` out of range for 18-bit immediate"))
}
fn check_imm24(u: u32) -> Imm24 {
Imm24::new_checked(u).unwrap_or_else(|| panic!("Value `{u}` out of range for 24-bit immediate"))
}
#[test]
fn test_instruction_size() {
assert_eq!(
core::mem::size_of::<Instruction>(),
core::mem::size_of::<RawInstruction>()
);
assert_eq!(core::mem::size_of::<Instruction>(), Instruction::SIZE);
}
#[test]
fn test_opcode_size() {
assert_eq!(core::mem::size_of::<Opcode>(), 1);
}
#[test]
#[allow(clippy::match_like_matches_macro)]
fn check_predicate_allowed() {
use Opcode::*;
for byte in 0..u8::MAX {
if let Ok(repr) = Opcode::try_from(byte) {
let should_allow = match repr {
BAL | BHEI | BHSH | BURN | CALL | CB | CCP | CROO | CSIZ | LDC | LOG | LOGD | MINT | RETD | RVRT
| SMO | SCWQ | SRW | SRWQ | SWW | SWWQ | TIME | TR | TRO => false,
_ => true,
};
assert_eq!(should_allow, repr.is_predicate_allowed());
}
}
}
#[test]
fn test_opcode_u8_conv() {
for u in 0..=u8::MAX {
if let Ok(op) = Opcode::try_from(u) {
assert_eq!(op as u8, u);
}
}
}