use fuel_asm::{
GTFArgs,
PanicReason,
RegId,
op,
};
use fuel_tx::{
ConsensusParameters,
Finalizable,
Receipt,
Script,
TransactionBuilder,
};
use fuel_vm::{
error::SimpleResult,
interpreter::{
EcalHandler,
Memory,
},
prelude::{
Interpreter,
IntoChecked,
MemoryClient,
},
storage::MemoryStorage,
};
use std::{
fs::{
self,
File,
},
io::{
Read,
Seek,
SeekFrom,
},
ops::Deref,
path::PathBuf,
sync::{
Arc,
Mutex,
},
};
#[derive(Debug, Clone, Copy, Default)]
pub struct FileReadEcal;
impl EcalHandler for FileReadEcal {
fn ecal<M, S, Tx, V>(
vm: &mut Interpreter<M, S, Tx, Self, V>,
a: RegId,
b: RegId,
c: RegId,
d: RegId,
) -> SimpleResult<()>
where
M: Memory,
{
let a = vm.registers()[a]; let b = vm.registers()[b]; let c = vm.registers()[c]; let d = vm.registers()[d];
vm.gas_charge(b.saturating_add(1))?;
let path = String::from_utf8_lossy(vm.memory().read(c, d)?);
let path = PathBuf::from(path.as_ref());
let mut file = File::open(path).map_err(|_| PanicReason::EcalError)?;
let _ = file
.seek(SeekFrom::Start(a))
.map_err(|_| PanicReason::EcalError)?;
vm.allocate(b)?;
let hp = vm.registers()[RegId::HP];
file.read(vm.memory_mut().write_noownerchecks(hp, b)?)
.map_err(|_| PanicReason::EcalError)?;
Ok(())
}
}
fn example_file_read() {
let vm: Interpreter<_, MemoryStorage, Script, FileReadEcal> =
Interpreter::with_memory_storage();
let script_data: Vec<u8> = file!().bytes().collect();
let script = vec![
op::movi(0x20, 4), op::movi(0x21, 8), op::gtf_args(0x22, 0x00, GTFArgs::ScriptData), op::movi(0x23, script_data.len().try_into().unwrap()), op::ecal(0x20, 0x21, 0x22, 0x23), op::logd(RegId::ZERO, RegId::ZERO, RegId::HP, 0x21), op::ret(RegId::ONE),
]
.into_iter()
.collect();
let mut client = MemoryClient::from_txtor(vm.into());
let consensus_params = ConsensusParameters::standard();
let tx = TransactionBuilder::script(script, script_data)
.script_gas_limit(1_000_000)
.maturity(Default::default())
.add_fee_input()
.finalize()
.into_checked(Default::default(), &consensus_params)
.expect("failed to generate a checked tx");
client.transact(tx);
let receipts = client.receipts().expect("Expected receipts");
let Receipt::LogData { data, .. } = receipts.first().unwrap() else {
panic!("Expected a data log receipt");
};
let read_bytes = data.as_ref().unwrap().deref();
let expected_bytes = &fs::read(file!()).expect("Couldn't read")[4..12];
assert_eq!(read_bytes, expected_bytes);
}
#[derive(Debug, Clone, Default)]
pub struct CounterEcal {
counter: u64,
}
impl EcalHandler for CounterEcal {
fn ecal<M, S, Tx, V>(
vm: &mut Interpreter<M, S, Tx, Self, V>,
a: RegId,
_b: RegId,
_c: RegId,
_d: RegId,
) -> SimpleResult<()>
where
M: Memory,
{
vm.registers_mut()[a] = vm.ecal_state().counter;
vm.ecal_state_mut().counter += 1;
vm.gas_charge(1)?;
Ok(())
}
}
fn example_counter() {
let mut vm: Interpreter<_, MemoryStorage, Script, CounterEcal> =
Interpreter::with_memory_storage();
vm.ecal_state_mut().counter = 5;
let script_data: Vec<u8> = file!().bytes().collect();
let script = vec![
op::ecal(0x20, RegId::ZERO, RegId::ZERO, RegId::ZERO),
op::ecal(0x21, RegId::ZERO, RegId::ZERO, RegId::ZERO),
op::ecal(0x22, RegId::ZERO, RegId::ZERO, RegId::ZERO),
op::ecal(0x23, RegId::ZERO, RegId::ZERO, RegId::ZERO),
op::log(0x20, 0x21, 0x22, 0x23),
op::ret(RegId::ONE),
]
.into_iter()
.collect();
let mut client = MemoryClient::from_txtor(vm.into());
let consensus_params = ConsensusParameters::standard();
let tx = TransactionBuilder::script(script, script_data)
.script_gas_limit(1_000_000)
.maturity(Default::default())
.add_fee_input()
.finalize()
.into_checked(Default::default(), &consensus_params)
.expect("failed to generate a checked tx");
client.transact(tx);
let receipts = client.receipts().expect("Expected receipts");
let Receipt::Log { ra, rb, rc, rd, .. } = receipts.first().unwrap() else {
panic!("Expected a log receipt");
};
assert_eq!(*ra, 5);
assert_eq!(*rb, 6);
assert_eq!(*rc, 7);
assert_eq!(*rd, 8);
}
#[derive(Debug, Clone)]
pub struct SharedCounterEcal {
counter: Arc<Mutex<u64>>,
}
impl EcalHandler for SharedCounterEcal {
fn ecal<M, S, Tx, V>(
vm: &mut Interpreter<M, S, Tx, Self, V>,
a: RegId,
_b: RegId,
_c: RegId,
_d: RegId,
) -> SimpleResult<()> {
let mut counter = vm.ecal_state().counter.lock().expect("poisoned");
let old_value = *counter;
*counter += 1;
drop(counter);
vm.registers_mut()[a] = old_value;
vm.gas_charge(1)?;
Ok(())
}
}
fn example_shared_counter() {
let vm: Interpreter<_, MemoryStorage, Script, SharedCounterEcal> =
Interpreter::with_memory_storage_and_ecal(SharedCounterEcal {
counter: Arc::new(Mutex::new(5)),
});
let script_data: Vec<u8> = file!().bytes().collect();
let script = vec![
op::ecal(0x20, RegId::ZERO, RegId::ZERO, RegId::ZERO),
op::ecal(0x21, RegId::ZERO, RegId::ZERO, RegId::ZERO),
op::ecal(0x22, RegId::ZERO, RegId::ZERO, RegId::ZERO),
op::ecal(0x23, RegId::ZERO, RegId::ZERO, RegId::ZERO),
op::log(0x20, 0x21, 0x22, 0x23),
op::ret(RegId::ONE),
]
.into_iter()
.collect();
let mut client = MemoryClient::from_txtor(vm.into());
let consensus_params = ConsensusParameters::standard();
let tx = TransactionBuilder::script(script, script_data)
.script_gas_limit(1_000_000)
.maturity(Default::default())
.add_fee_input()
.finalize()
.into_checked(Default::default(), &consensus_params)
.expect("failed to generate a checked tx");
client.transact(tx);
let receipts = client.receipts().expect("Expected receipts");
let Receipt::Log { ra, rb, rc, rd, .. } = receipts.first().unwrap() else {
panic!("Expected a log receipt");
};
assert_eq!(*ra, 5);
assert_eq!(*rb, 6);
assert_eq!(*rc, 7);
assert_eq!(*rd, 8);
}
fn main() {
example_file_read();
example_counter();
example_shared_counter();
}