use crate::{
call::CallFrame,
constraints::reg_key::*,
context::Context,
error::{
IoResult,
RuntimeError,
SimpleResult,
},
interpreter::{
ExecutableTransaction,
Interpreter,
Memory,
MemoryInstance,
RuntimeBalances,
contract::{
balance,
balance_decrease,
blob_size,
contract_size,
},
gas::{
dependent_gas_charge_without_base,
gas_charge,
},
internal::{
base_asset_balance_sub,
inc_pc,
internal_contract,
tx_id,
},
memory::{
OwnershipRegisters,
copy_from_storage_zero_fill,
},
receipts::ReceiptsCtx,
},
storage::{
BlobData,
ContractsAssetsStorage,
ContractsRawCode,
InterpreterStorage,
},
verification::Verifier,
};
use alloc::collections::BTreeSet;
use fuel_asm::{
Imm06,
PanicReason,
RegId,
};
use fuel_storage::StorageSize;
use fuel_tx::{
BlobId,
ContractIdExt,
DependentCost,
Receipt,
consts::BALANCE_ENTRY_SIZE,
};
use fuel_types::{
Address,
AssetId,
BlockHeight,
Bytes32,
ContractId,
SubAssetId,
Word,
bytes::{
self,
padded_len_word,
},
};
use super::PanicContext;
#[cfg(test)]
mod code_tests;
#[cfg(test)]
mod croo_tests;
#[cfg(test)]
mod other_tests;
#[cfg(test)]
mod smo_tests;
#[cfg(test)]
mod test;
impl<M, S, Tx, Ecal, V> Interpreter<M, S, Tx, Ecal, V>
where
M: Memory,
Tx: ExecutableTransaction,
S: InterpreterStorage,
V: Verifier,
{
pub(crate) fn load_contract_code(
&mut self,
addr: Word,
offset: Word,
length_unpadded: Word,
mode: Imm06,
) -> IoResult<(), S::DataError> {
let gas_cost = self.gas_costs().ldc();
self.gas_charge(gas_cost.base())?;
let contract_max_size = self.contract_max_size();
let (
SystemRegisters {
cgas,
ggas,
ssp,
sp,
hp,
fp,
pc,
..
},
_,
) = split_registers(&mut self.registers);
let input = LoadContractCodeCtx {
memory: self.memory.as_mut(),
context: &self.context,
storage: &mut self.storage,
contract_max_size,
input_contracts: &self.input_contracts,
panic_context: &mut self.panic_context,
gas_cost,
cgas,
ggas,
ssp,
sp,
hp: hp.as_ref(),
fp: fp.as_ref(),
pc,
verifier: &mut self.verifier,
};
match mode.to_u8() {
0 => input.load_contract_code(addr, offset, length_unpadded),
1 => input.load_blob_code(addr, offset, length_unpadded),
2 => input.load_memory_code(addr, offset, length_unpadded),
_ => Err(PanicReason::InvalidImmediateValue.into()),
}
}
pub(crate) fn burn(&mut self, a: Word, b: Word) -> IoResult<(), S::DataError> {
let (SystemRegisters { fp, pc, is, .. }, _) =
split_registers(&mut self.registers);
BurnCtx {
storage: &mut self.storage,
context: &self.context,
memory: self.memory.as_ref(),
receipts: &mut self.receipts,
fp: fp.as_ref(),
pc,
is: is.as_ref(),
}
.burn(a, b)
}
pub(crate) fn mint(&mut self, a: Word, b: Word) -> IoResult<(), S::DataError> {
let new_storage_gas_per_byte = self.gas_costs().new_storage_per_byte();
let (
SystemRegisters {
cgas,
ggas,
fp,
pc,
is,
..
},
_,
) = split_registers(&mut self.registers);
MintCtx {
storage: &mut self.storage,
context: &self.context,
memory: self.memory.as_ref(),
receipts: &mut self.receipts,
new_storage_gas_per_byte,
cgas,
ggas,
fp: fp.as_ref(),
pc,
is: is.as_ref(),
}
.mint(a, b)
}
pub(crate) fn code_copy(
&mut self,
a: Word,
b: Word,
c: Word,
d: Word,
) -> IoResult<(), S::DataError> {
let gas_cost = self.gas_costs().ccp();
self.gas_charge(gas_cost.base())?;
let owner = self.ownership_registers();
let (SystemRegisters { cgas, ggas, pc, .. }, _) =
split_registers(&mut self.registers);
let input = CodeCopyCtx {
memory: self.memory.as_mut(),
input_contracts: &self.input_contracts,
panic_context: &mut self.panic_context,
storage: &mut self.storage,
owner,
gas_cost,
cgas,
ggas,
pc,
verifier: &mut self.verifier,
};
input.code_copy(a, b, c, d)
}
pub(crate) fn block_hash(&mut self, a: Word, b: Word) -> IoResult<(), S::DataError> {
let owner = self.ownership_registers();
block_hash(
&self.storage,
self.memory.as_mut(),
owner,
self.registers.pc_mut(),
a,
b,
)
}
pub(crate) fn block_height(&mut self, ra: RegId) -> IoResult<(), S::DataError> {
let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers);
let result = &mut w[WriteRegKey::try_from(ra)?];
Ok(block_height(&self.context, pc, result)?)
}
pub(crate) fn block_proposer(&mut self, a: Word) -> IoResult<(), S::DataError> {
let owner = self.ownership_registers();
coinbase(
&self.storage,
self.memory.as_mut(),
owner,
self.registers.pc_mut(),
a,
)
}
pub(crate) fn code_root(&mut self, a: Word, b: Word) -> IoResult<(), S::DataError> {
let gas_cost = self.gas_costs().croo();
self.gas_charge(gas_cost.base())?;
let owner = self.ownership_registers();
let (SystemRegisters { cgas, ggas, pc, .. }, _) =
split_registers(&mut self.registers);
CodeRootCtx {
memory: self.memory.as_mut(),
storage: &mut self.storage,
gas_cost,
input_contracts: &self.input_contracts,
panic_context: &mut self.panic_context,
cgas,
ggas,
owner,
pc,
verifier: &mut self.verifier,
}
.code_root(a, b)
}
pub(crate) fn code_size(&mut self, ra: RegId, b: Word) -> IoResult<(), S::DataError> {
let gas_cost = self.gas_costs().csiz();
self.gas_charge(gas_cost.base())?;
let (SystemRegisters { cgas, ggas, pc, .. }, mut w) =
split_registers(&mut self.registers);
let result = &mut w[WriteRegKey::try_from(ra)?];
let input = CodeSizeCtx {
memory: self.memory.as_mut(),
storage: &mut self.storage,
gas_cost,
input_contracts: &self.input_contracts,
panic_context: &mut self.panic_context,
cgas,
ggas,
pc,
verifier: &mut self.verifier,
};
input.code_size(result, b)
}
pub(crate) fn timestamp(&mut self, ra: RegId, b: Word) -> IoResult<(), S::DataError> {
let block_height = self.get_block_height()?;
let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers);
let result = &mut w[WriteRegKey::try_from(ra)?];
timestamp(&self.storage, block_height, pc, result, b)
}
pub(crate) fn message_output(
&mut self,
a: Word,
b: Word,
c: Word,
d: Word,
) -> IoResult<(), S::DataError> {
let base_asset_id = self.interpreter_params.base_asset_id;
let max_message_data_length = self.max_message_data_length();
let (SystemRegisters { fp, pc, .. }, _) = split_registers(&mut self.registers);
let input = MessageOutputCtx {
base_asset_id,
max_message_data_length,
memory: self.memory.as_mut(),
receipts: &mut self.receipts,
balances: &mut self.balances,
storage: &mut self.storage,
current_contract: self.frames.last().map(|frame| frame.to()).copied(),
fp: fp.as_ref(),
pc,
recipient_mem_address: a,
msg_data_ptr: b,
msg_data_len: c,
amount_coins_to_send: d,
};
input.message_output()
}
}
struct LoadContractCodeCtx<'vm, S, V> {
contract_max_size: u64,
memory: &'vm mut MemoryInstance,
context: &'vm Context,
input_contracts: &'vm BTreeSet<ContractId>,
panic_context: &'vm mut PanicContext,
storage: &'vm S,
gas_cost: DependentCost,
cgas: RegMut<'vm, CGAS>,
ggas: RegMut<'vm, GGAS>,
ssp: RegMut<'vm, SSP>,
sp: RegMut<'vm, SP>,
hp: Reg<'vm, HP>,
fp: Reg<'vm, FP>,
pc: RegMut<'vm, PC>,
verifier: &'vm mut V,
}
impl<S, V> LoadContractCodeCtx<'_, S, V> {
pub(crate) fn load_contract_code(
mut self,
contract_id_addr: Word,
contract_offset: Word,
length_unpadded: Word,
) -> IoResult<(), S::DataError>
where
S: InterpreterStorage,
V: Verifier,
{
let ssp = *self.ssp;
let sp = *self.sp;
let region_start = ssp;
if self.context.is_predicate() {
return Err(PanicReason::ContractInstructionNotAllowed.into())
}
if ssp != sp {
return Err(PanicReason::ExpectedUnallocatedStack.into())
}
let contract_id = ContractId::from(self.memory.read_bytes(contract_id_addr)?);
let length =
padded_len_word(length_unpadded).ok_or(PanicReason::MemoryOverflow)?;
if length > self.contract_max_size {
return Err(PanicReason::ContractMaxSize.into())
}
self.verifier.check_contract_in_inputs(
self.panic_context,
self.input_contracts,
&contract_id,
)?;
let contract_len = contract_size(&self.storage, &contract_id)?;
let charge_len = core::cmp::max(contract_len as u64, length);
dependent_gas_charge_without_base(
self.cgas,
self.ggas,
self.gas_cost,
charge_len,
)?;
let new_sp = ssp.saturating_add(length);
self.memory.grow_stack(new_sp)?;
let owner =
OwnershipRegisters::only_allow_stack_write(new_sp, *self.ssp, *self.hp);
*self.sp = new_sp;
*self.ssp = new_sp;
copy_from_storage_zero_fill::<ContractsRawCode, _>(
self.memory,
owner,
self.storage,
region_start,
length,
&contract_id,
contract_offset,
contract_len,
PanicReason::ContractNotFound,
)?;
if self.context.is_internal() {
let code_size_ptr =
(*self.fp).saturating_add(CallFrame::code_size_offset() as Word);
let old_code_size =
Word::from_be_bytes(self.memory.read_bytes(code_size_ptr)?);
let old_code_size =
padded_len_word(old_code_size).ok_or(PanicReason::MemoryOverflow)?;
let new_code_size = old_code_size
.checked_add(length as Word)
.ok_or(PanicReason::MemoryOverflow)?;
self.memory
.write_bytes_noownerchecks(code_size_ptr, new_code_size.to_be_bytes())?;
}
inc_pc(self.pc);
Ok(())
}
pub(crate) fn load_blob_code(
mut self,
blob_id_addr: Word,
blob_offset: Word,
length_unpadded: Word,
) -> IoResult<(), S::DataError>
where
S: InterpreterStorage,
{
let ssp = *self.ssp;
let sp = *self.sp;
let region_start = ssp;
if ssp != sp {
return Err(PanicReason::ExpectedUnallocatedStack.into())
}
let blob_id = BlobId::from(self.memory.read_bytes(blob_id_addr)?);
let length = bytes::padded_len_word(length_unpadded).unwrap_or(Word::MAX);
let blob_len = blob_size(self.storage, &blob_id)?;
let charge_len = core::cmp::max(blob_len as u64, length);
dependent_gas_charge_without_base(
self.cgas,
self.ggas,
self.gas_cost,
charge_len,
)?;
let new_sp = ssp.saturating_add(length);
self.memory.grow_stack(new_sp)?;
let owner =
OwnershipRegisters::only_allow_stack_write(new_sp, *self.ssp, *self.hp);
*self.sp = new_sp;
*self.ssp = new_sp;
copy_from_storage_zero_fill::<BlobData, _>(
self.memory,
owner,
self.storage,
region_start,
length,
&blob_id,
blob_offset,
blob_len,
PanicReason::BlobNotFound,
)?;
if self.context.is_internal() {
let code_size_ptr =
(*self.fp).saturating_add(CallFrame::code_size_offset() as Word);
let old_code_size =
Word::from_be_bytes(self.memory.read_bytes(code_size_ptr)?);
let old_code_size = padded_len_word(old_code_size)
.expect("Code size cannot overflow with padding");
let new_code_size = old_code_size
.checked_add(length as Word)
.ok_or(PanicReason::MemoryOverflow)?;
self.memory
.write_bytes_noownerchecks(code_size_ptr, new_code_size.to_be_bytes())?;
}
inc_pc(self.pc);
Ok(())
}
pub(crate) fn load_memory_code(
mut self,
input_src_addr: Word,
input_offset: Word,
length_unpadded: Word,
) -> IoResult<(), S::DataError>
where
S: InterpreterStorage,
{
let ssp = *self.ssp;
let sp = *self.sp;
let dst = ssp;
if ssp != sp {
return Err(PanicReason::ExpectedUnallocatedStack.into())
}
if length_unpadded == 0 {
inc_pc(self.pc);
return Ok(())
}
let length = bytes::padded_len_word(length_unpadded).unwrap_or(Word::MAX);
let length_padding = length.saturating_sub(length_unpadded);
let charge_len = length;
dependent_gas_charge_without_base(
self.cgas,
self.ggas,
self.gas_cost,
charge_len,
)?;
let new_sp = ssp.saturating_add(length);
self.memory.grow_stack(new_sp)?;
let owner = OwnershipRegisters::only_allow_stack_write(new_sp, ssp, *self.hp);
let src = input_src_addr.saturating_add(input_offset);
self.memory.memcopy(dst, src, length_unpadded, owner)?;
if length_padding > 0 {
self.memory
.write(owner, dst.saturating_add(length_unpadded), length_padding)?
.fill(0);
}
*self.sp = new_sp;
*self.ssp = new_sp;
if self.context.is_internal() {
let code_size_ptr =
(*self.fp).saturating_add(CallFrame::code_size_offset() as Word);
let old_code_size =
Word::from_be_bytes(self.memory.read_bytes(code_size_ptr)?);
let old_code_size = padded_len_word(old_code_size)
.expect("Code size cannot overflow with padding");
let new_code_size = old_code_size
.checked_add(length as Word)
.ok_or(PanicReason::MemoryOverflow)?;
self.memory
.write_bytes_noownerchecks(code_size_ptr, new_code_size.to_be_bytes())?;
}
inc_pc(self.pc);
Ok(())
}
}
struct BurnCtx<'vm, S> {
storage: &'vm mut S,
context: &'vm Context,
memory: &'vm MemoryInstance,
receipts: &'vm mut ReceiptsCtx,
fp: Reg<'vm, FP>,
pc: RegMut<'vm, PC>,
is: Reg<'vm, IS>,
}
impl<S> BurnCtx<'_, S>
where
S: ContractsAssetsStorage,
{
pub(crate) fn burn(self, a: Word, b: Word) -> IoResult<(), S::Error> {
let contract_id = internal_contract(self.context, self.fp, self.memory)?;
let sub_id = SubAssetId::new(self.memory.read_bytes(b)?);
let asset_id = contract_id.asset_id(&sub_id);
let balance = balance(self.storage, &contract_id, &asset_id)?;
let balance = balance
.checked_sub(a)
.ok_or(PanicReason::NotEnoughBalance)?;
self.storage
.contract_asset_id_balance_insert(&contract_id, &asset_id, balance)
.map_err(RuntimeError::Storage)?;
let receipt = Receipt::burn(sub_id, contract_id, a, *self.pc, *self.is);
self.receipts.push(receipt)?;
inc_pc(self.pc);
Ok(())
}
}
struct MintCtx<'vm, S> {
storage: &'vm mut S,
context: &'vm Context,
memory: &'vm MemoryInstance,
receipts: &'vm mut ReceiptsCtx,
new_storage_gas_per_byte: Word,
cgas: RegMut<'vm, CGAS>,
ggas: RegMut<'vm, GGAS>,
fp: Reg<'vm, FP>,
pc: RegMut<'vm, PC>,
is: Reg<'vm, IS>,
}
impl<S> MintCtx<'_, S>
where
S: ContractsAssetsStorage,
{
pub(crate) fn mint(self, a: Word, b: Word) -> Result<(), RuntimeError<S::Error>> {
let contract_id = internal_contract(self.context, self.fp, self.memory)?;
let sub_id = SubAssetId::new(self.memory.read_bytes(b)?);
let asset_id = contract_id.asset_id(&sub_id);
let balance = balance(self.storage, &contract_id, &asset_id)?;
let balance = balance.checked_add(a).ok_or(PanicReason::BalanceOverflow)?;
let old_value = self
.storage
.contract_asset_id_balance_replace(&contract_id, &asset_id, balance)
.map_err(RuntimeError::Storage)?;
if old_value.is_none() {
gas_charge(
self.cgas,
self.ggas,
(BALANCE_ENTRY_SIZE as u64).saturating_mul(self.new_storage_gas_per_byte),
)?;
}
let receipt = Receipt::mint(sub_id, contract_id, a, *self.pc, *self.is);
self.receipts.push(receipt)?;
inc_pc(self.pc);
Ok(())
}
}
struct CodeCopyCtx<'vm, S, V> {
memory: &'vm mut MemoryInstance,
input_contracts: &'vm BTreeSet<ContractId>,
panic_context: &'vm mut PanicContext,
storage: &'vm S,
owner: OwnershipRegisters,
gas_cost: DependentCost,
cgas: RegMut<'vm, CGAS>,
ggas: RegMut<'vm, GGAS>,
pc: RegMut<'vm, PC>,
verifier: &'vm mut V,
}
impl<S, V> CodeCopyCtx<'_, S, V> {
pub(crate) fn code_copy(
self,
dst_addr: Word,
contract_id_addr: Word,
contract_offset: Word,
length: Word,
) -> IoResult<(), S::DataError>
where
S: InterpreterStorage,
V: Verifier,
{
let contract_id = ContractId::from(self.memory.read_bytes(contract_id_addr)?);
self.memory.write(self.owner, dst_addr, length)?;
self.verifier.check_contract_in_inputs(
self.panic_context,
self.input_contracts,
&contract_id,
)?;
let contract_len = contract_size(&self.storage, &contract_id)?;
let charge_len = core::cmp::max(contract_len as u64, length);
dependent_gas_charge_without_base(
self.cgas,
self.ggas,
self.gas_cost,
charge_len,
)?;
copy_from_storage_zero_fill::<ContractsRawCode, _>(
self.memory,
self.owner,
self.storage,
dst_addr,
length,
&contract_id,
contract_offset,
contract_len,
PanicReason::ContractNotFound,
)?;
inc_pc(self.pc);
Ok(())
}
}
pub(crate) fn block_hash<S: InterpreterStorage>(
storage: &S,
memory: &mut MemoryInstance,
owner: OwnershipRegisters,
pc: RegMut<PC>,
a: Word,
b: Word,
) -> IoResult<(), S::DataError> {
let height = u32::try_from(b)
.map_err(|_| PanicReason::InvalidBlockHeight)?
.into();
let hash = storage.block_hash(height).map_err(RuntimeError::Storage)?;
memory.write_bytes(owner, a, *hash)?;
inc_pc(pc);
Ok(())
}
pub(crate) fn block_height(
context: &Context,
pc: RegMut<PC>,
result: &mut Word,
) -> SimpleResult<()> {
context
.block_height()
.map(|h| *h as Word)
.map(|h| *result = h)
.ok_or(PanicReason::TransactionValidity)?;
inc_pc(pc);
Ok(())
}
pub(crate) fn coinbase<S: InterpreterStorage>(
storage: &S,
memory: &mut MemoryInstance,
owner: OwnershipRegisters,
pc: RegMut<PC>,
a: Word,
) -> IoResult<(), S::DataError> {
let coinbase = storage.coinbase().map_err(RuntimeError::Storage)?;
memory.write_bytes(owner, a, *coinbase)?;
inc_pc(pc);
Ok(())
}
struct CodeRootCtx<'vm, S, V> {
storage: &'vm S,
memory: &'vm mut MemoryInstance,
gas_cost: DependentCost,
input_contracts: &'vm BTreeSet<ContractId>,
panic_context: &'vm mut PanicContext,
cgas: RegMut<'vm, CGAS>,
ggas: RegMut<'vm, GGAS>,
owner: OwnershipRegisters,
pc: RegMut<'vm, PC>,
verifier: &'vm mut V,
}
impl<S, V> CodeRootCtx<'_, S, V> {
pub(crate) fn code_root(self, a: Word, b: Word) -> IoResult<(), S::DataError>
where
S: InterpreterStorage,
V: Verifier,
{
self.memory.write_noownerchecks(a, Bytes32::LEN)?;
let contract_id = ContractId::new(self.memory.read_bytes(b)?);
self.verifier.check_contract_in_inputs(
self.panic_context,
self.input_contracts,
&contract_id,
)?;
let len = contract_size(self.storage, &contract_id)?;
dependent_gas_charge_without_base(
self.cgas,
self.ggas,
self.gas_cost,
len as u64,
)?;
let root = self
.storage
.storage_contract(&contract_id)
.transpose()
.ok_or(PanicReason::ContractNotFound)?
.map_err(RuntimeError::Storage)?
.root();
self.memory.write_bytes(self.owner, a, *root)?;
inc_pc(self.pc);
Ok(())
}
}
struct CodeSizeCtx<'vm, S, V> {
storage: &'vm S,
memory: &'vm mut MemoryInstance,
gas_cost: DependentCost,
input_contracts: &'vm BTreeSet<ContractId>,
panic_context: &'vm mut PanicContext,
cgas: RegMut<'vm, CGAS>,
ggas: RegMut<'vm, GGAS>,
pc: RegMut<'vm, PC>,
verifier: &'vm mut V,
}
impl<S, V> CodeSizeCtx<'_, S, V> {
pub(crate) fn code_size(
self,
result: &mut Word,
b: Word,
) -> Result<(), RuntimeError<S::Error>>
where
S: StorageSize<ContractsRawCode>,
V: Verifier,
{
let contract_id = ContractId::new(self.memory.read_bytes(b)?);
self.verifier.check_contract_in_inputs(
self.panic_context,
self.input_contracts,
&contract_id,
)?;
let len = contract_size(self.storage, &contract_id)?;
dependent_gas_charge_without_base(
self.cgas,
self.ggas,
self.gas_cost,
len as u64,
)?;
*result = len as u64;
inc_pc(self.pc);
Ok(())
}
}
pub(crate) fn timestamp<S: InterpreterStorage>(
storage: &S,
block_height: BlockHeight,
pc: RegMut<PC>,
result: &mut Word,
b: Word,
) -> IoResult<(), S::DataError> {
let b = u32::try_from(b)
.map_err(|_| PanicReason::InvalidBlockHeight)?
.into();
(b <= block_height)
.then_some(())
.ok_or(PanicReason::TransactionValidity)?;
*result = storage.timestamp(b).map_err(RuntimeError::Storage)?;
inc_pc(pc);
Ok(())
}
struct MessageOutputCtx<'vm, S>
where
S: ContractsAssetsStorage + ?Sized,
{
base_asset_id: AssetId,
max_message_data_length: u64,
memory: &'vm mut MemoryInstance,
receipts: &'vm mut ReceiptsCtx,
balances: &'vm mut RuntimeBalances,
storage: &'vm mut S,
current_contract: Option<ContractId>,
fp: Reg<'vm, FP>,
pc: RegMut<'vm, PC>,
recipient_mem_address: Word,
msg_data_ptr: Word,
msg_data_len: Word,
amount_coins_to_send: Word,
}
impl<S> MessageOutputCtx<'_, S>
where
S: ContractsAssetsStorage + ?Sized,
{
pub(crate) fn message_output(self) -> Result<(), RuntimeError<S::Error>> {
if self.msg_data_len > self.max_message_data_length {
return Err(RuntimeError::Recoverable(PanicReason::MessageDataTooLong));
}
let msg_data = self
.memory
.read(self.msg_data_ptr, self.msg_data_len)?
.to_vec();
let recipient = Address::new(self.memory.read_bytes(self.recipient_mem_address)?);
let sender = Address::new(self.memory.read_bytes(*self.fp)?);
if let Some(source_contract) = self.current_contract {
balance_decrease(
self.storage,
&source_contract,
&self.base_asset_id,
self.amount_coins_to_send,
)?;
} else {
base_asset_balance_sub(
&self.base_asset_id,
self.balances,
self.memory,
self.amount_coins_to_send,
)?;
}
let txid = tx_id(self.memory);
let receipt = Receipt::message_out(
&txid,
self.receipts.len() as Word,
sender,
recipient,
self.amount_coins_to_send,
msg_data,
);
self.receipts.push(receipt)?;
inc_pc(self.pc);
Ok(())
}
}