use std::{
fs::{
self,
File,
},
io::{
Read,
Seek,
SeekFrom,
},
path::PathBuf,
sync::{
Arc,
Mutex,
},
};
use fuel_asm::{
op,
GTFArgs,
PanicReason,
RegId,
};
use fuel_tx::{
ConsensusParameters,
Finalizable,
Receipt,
Script,
TransactionBuilder,
};
use fuel_vm::{
error::SimpleResult,
interpreter::EcalHandler,
prelude::{
Interpreter,
IntoChecked,
MemoryClient,
MemoryRange,
},
storage::MemoryStorage,
};
#[derive(Debug, Clone, Copy, Default)]
pub struct FileReadEcal;
impl EcalHandler for FileReadEcal {
fn ecal<S, Tx>(
vm: &mut Interpreter<S, Tx, Self>,
a: RegId,
b: RegId,
c: RegId,
d: RegId,
) -> SimpleResult<()> {
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 r = MemoryRange::new(c, d)?;
let path = String::from_utf8_lossy(&vm.memory()[r.usizes()]);
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 r = MemoryRange::new(vm.registers()[RegId::HP], b)?;
file.read(&mut vm.memory_mut()[r.usizes()])
.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)
.gas_price(0)
.script_gas_limit(1_000_000)
.maturity(Default::default())
.add_random_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();
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<S, Tx>(
vm: &mut Interpreter<S, Tx, Self>,
a: RegId,
_b: RegId,
_c: RegId,
_d: RegId,
) -> SimpleResult<()> {
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)
.gas_price(0)
.script_gas_limit(1_000_000)
.maturity(Default::default())
.add_random_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<S, Tx>(
vm: &mut Interpreter<S, Tx, Self>,
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)
.gas_price(0)
.script_gas_limit(1_000_000)
.maturity(Default::default())
.add_random_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();
}