1pub mod constants;
2
3pub mod spawn;
4pub use spawn::*;
5
6mod global_data;
7mod simulator_context;
8mod utils;
9
10use global_data::GlobalData;
11use simulator_context::SimContext;
12
13#[macro_use]
14extern crate lazy_static;
15
16use ckb_mock_tx_types::{MockTransaction, ReprMockTransaction};
17use ckb_types::{
18 bytes::Bytes,
19 core::{Capacity, HeaderView, cell::CellMetaBuilder},
20 packed::{self, Byte32, CellInput, CellOutput, Script},
21 prelude::*,
22};
23use constants::{
24 CELL_FIELD_CAPACITY, CELL_FIELD_DATA_HASH, CELL_FIELD_LOCK, CELL_FIELD_LOCK_HASH, CELL_FIELD_OCCUPIED_CAPACITY,
25 CELL_FIELD_TYPE, CELL_FIELD_TYPE_HASH, CKB_INDEX_OUT_OF_BOUND, CKB_ITEM_MISSING, CKB_SUCCESS,
26 HEADER_FIELD_EPOCH_LENGTH, HEADER_FIELD_EPOCH_NUMBER, HEADER_FIELD_EPOCH_START_BLOCK_NUMBER, INPUT_FIELD_OUT_POINT,
27 INPUT_FIELD_SINCE, SOURCE_CELL_DEP, SOURCE_GROUP_CELL_DEP, SOURCE_GROUP_HEADER_DEP, SOURCE_GROUP_INPUT,
28 SOURCE_GROUP_OUTPUT, SOURCE_HEADER_DEP, SOURCE_INPUT, SOURCE_OUTPUT,
29};
30use serde_derive::{Deserialize, Serialize};
31use std::collections::HashMap;
32use std::ffi::CString;
33use std::os::raw::{c_char, c_int, c_void};
34
35#[derive(Clone, Serialize, Deserialize)]
36pub enum RunningType {
37 Executable,
38 DynamicLib,
39}
40
41#[derive(Clone, Serialize, Deserialize)]
42pub struct RunningSetup {
43 pub is_lock_script: bool,
44 pub is_output: bool,
45 pub script_index: u64,
46 pub vm_version: i32,
47 pub native_binaries: HashMap<String, String>,
48 pub run_type: Option<RunningType>,
49}
50
51lazy_static! {
52 static ref TRANSACTION: MockTransaction = {
53 let tx_filename = std::env::var("CKB_TX_FILE").expect("environment variable");
54 let tx_content = std::fs::read_to_string(tx_filename).expect("read tx file");
55 let repr_mock_tx: ReprMockTransaction = serde_json::from_str(&tx_content).expect("parse tx file");
56 let mock_tx: MockTransaction = repr_mock_tx.into();
57 mock_tx
58 };
59 static ref SETUP: RunningSetup = {
60 let setup_filename = std::env::var("CKB_RUNNING_SETUP").expect("environment variable");
61 let setup_content = std::fs::read_to_string(setup_filename).expect("read setup file");
62 serde_json::from_str(&setup_content).expect("parse setup file")
63 };
64}
65
66fn assert_vm_version() {
67 if SETUP.vm_version == 0 {
68 panic!("Currently running setup vm_version({}) not support this syscall", SETUP.vm_version);
69 }
70}
71
72#[unsafe(no_mangle)]
73pub extern "C" fn ckb_exit(code: i8) -> i32 {
74 std::process::exit(code.into());
75}
76
77#[unsafe(no_mangle)]
78pub extern "C" fn ckb_vm_version() -> c_int {
79 assert_vm_version();
80 SETUP.vm_version
81}
82
83#[unsafe(no_mangle)]
84pub extern "C" fn ckb_current_cycles() -> u64 {
85 assert_vm_version();
86 333
88}
89
90#[unsafe(no_mangle)]
92pub extern "C" fn ckb_exec_cell(
93 code_hash: *const u8,
94 hash_type: u8,
95 offset: u32,
96 length: u32,
97 argc: i32,
98 argv: *const *const u8,
99) -> c_int {
100 assert_vm_version();
101
102 let sim_path = utils::get_simulator_path(utils::to_array(code_hash, 32), hash_type, offset, length);
103 let sim_path = sim_path.expect("cannot locate native binary for ckb_exec syscall!");
104
105 match SETUP.run_type.as_ref().unwrap_or(&RunningType::Executable) {
106 RunningType::Executable => {
107 let filename_cstring = CString::new(sim_path.as_bytes().to_vec()).unwrap();
108 unsafe {
109 let args = argv as *const *const i8;
110 libc::execvp(filename_cstring.as_ptr(), args)
111 }
112 }
113 RunningType::DynamicLib => {
114 use utils::CkbNativeSimulator;
115
116 let tx_ctx_id = GlobalData::locked().set_tx(simulator_context::SimContext::default());
117 SimContext::update_ctx_id(tx_ctx_id.clone(), None);
118
119 let sim = CkbNativeSimulator::new_by_hash(code_hash, hash_type, offset, length);
120 let args = utils::to_vec_args(argc, argv as *const *const i8);
121
122 let join_handle = {
123 let mut global_data = GlobalData::locked();
124 let sim_ctx = global_data.get_tx_mut(&tx_ctx_id);
125 let child_pid: utils::ProcID = sim_ctx.start_process(&[], move |sim_id, pid| {
126 sim.update_script_info(sim_id, pid);
127 sim.ckb_std_main(args)
128 });
129 sim_ctx.exit(&child_pid).unwrap()
130 };
131 join_handle.join().expect("exec dylib") as c_int
132 }
133 }
134}
135
136#[unsafe(no_mangle)]
137pub extern "C" fn ckb_load_tx_hash(ptr: *mut c_void, len: *mut u64, offset: u64) -> c_int {
138 let view = TRANSACTION.tx.clone().into_view();
139 store_data(ptr, len, offset, view.hash().as_slice());
140 CKB_SUCCESS
141}
142
143#[unsafe(no_mangle)]
144pub extern "C" fn ckb_load_transaction(ptr: *mut c_void, len: *mut u64, offset: u64) -> c_int {
145 store_data(ptr, len, offset, TRANSACTION.tx.as_slice());
146 CKB_SUCCESS
147}
148
149#[unsafe(no_mangle)]
150pub extern "C" fn ckb_load_script_hash(ptr: *mut c_void, len: *mut u64, offset: u64) -> c_int {
151 let hash = fetch_current_script().calc_script_hash();
152 store_data(ptr, len, offset, hash.as_slice());
153 CKB_SUCCESS
154}
155
156#[unsafe(no_mangle)]
157pub extern "C" fn ckb_load_script(ptr: *mut c_void, len: *mut u64, offset: u64) -> c_int {
158 store_data(ptr, len, offset, fetch_current_script().as_slice());
159 CKB_SUCCESS
160}
161
162#[unsafe(no_mangle)]
163pub extern "C" fn ckb_debug(s: *const c_char) {
164 let message = utils::to_c_str(s).to_str().expect("UTF8 error!");
165 println!("[contract debug] {}", message);
167}
168
169#[unsafe(no_mangle)]
170pub extern "C" fn ckb_load_cell(ptr: *mut c_void, len: *mut u64, offset: u64, index: u64, source: u64) -> c_int {
171 let (cell, _) = match fetch_cell(index, source) {
172 Ok(cell) => cell,
173 Err(code) => return code,
174 };
175 store_data(ptr, len, offset, cell.as_slice());
176 CKB_SUCCESS
177}
178
179#[unsafe(no_mangle)]
180pub extern "C" fn ckb_load_input(ptr: *mut c_void, len: *mut u64, offset: u64, index: u64, source: u64) -> c_int {
181 let input = match fetch_input(index, source) {
182 Ok(input) => input,
183 Err(code) => return code,
184 };
185 store_data(ptr, len, offset, input.as_slice());
186 CKB_SUCCESS
187}
188
189#[unsafe(no_mangle)]
190pub extern "C" fn ckb_load_header(ptr: *mut c_void, len: *mut u64, offset: u64, index: u64, source: u64) -> c_int {
191 let header = match fetch_header(index, source) {
192 Ok(input) => input,
193 Err(code) => return code,
194 };
195 store_data(ptr, len, offset, header.data().as_slice());
196 CKB_SUCCESS
197}
198
199#[unsafe(no_mangle)]
200pub extern "C" fn ckb_load_witness(ptr: *mut c_void, len: *mut u64, offset: u64, index: u64, source: u64) -> c_int {
201 let witness = match fetch_witness(index, source) {
202 Some(witness) => witness,
203 None => return CKB_INDEX_OUT_OF_BOUND,
204 };
205 store_data(ptr, len, offset, &witness.raw_data());
206 CKB_SUCCESS
207}
208
209#[unsafe(no_mangle)]
210pub extern "C" fn ckb_load_cell_by_field(
211 ptr: *mut c_void,
212 len: *mut u64,
213 offset: u64,
214 index: u64,
215 source: u64,
216 field: u64,
217) -> c_int {
218 let (cell, cell_data) = match fetch_cell(index, source) {
219 Ok(cell) => cell,
220 Err(code) => return code,
221 };
222 let cell_meta = CellMetaBuilder::from_cell_output(cell.clone(), cell_data.clone()).build();
223 match field {
224 CELL_FIELD_CAPACITY => {
225 let capacity: Capacity = cell.capacity().unpack();
226 let data = capacity.as_u64().to_le_bytes();
227 store_data(ptr, len, offset, &data[..]);
228 }
229 CELL_FIELD_DATA_HASH => {
230 let hash = CellOutput::calc_data_hash(&cell_data);
231 store_data(ptr, len, offset, hash.as_slice());
232 }
233 CELL_FIELD_OCCUPIED_CAPACITY => {
234 let data = cell_meta.occupied_capacity().expect("capacity error").as_u64().to_le_bytes();
235 store_data(ptr, len, offset, &data[..]);
236 }
237 CELL_FIELD_LOCK => {
238 let lock = cell.lock();
239 store_data(ptr, len, offset, lock.as_slice());
240 }
241 CELL_FIELD_LOCK_HASH => {
242 let hash = cell.calc_lock_hash();
243 store_data(ptr, len, offset, &hash.as_bytes());
244 }
245 CELL_FIELD_TYPE => match cell.type_().to_opt() {
246 Some(type_) => {
247 store_data(ptr, len, offset, type_.as_slice());
248 }
249 None => {
250 return CKB_ITEM_MISSING;
251 }
252 },
253 CELL_FIELD_TYPE_HASH => match cell.type_().to_opt() {
254 Some(type_) => {
255 let hash = type_.calc_script_hash();
256 store_data(ptr, len, offset, &hash.as_bytes());
257 }
258 None => {
259 return CKB_ITEM_MISSING;
260 }
261 },
262 _ => panic!("Invalid field: {}", field),
263 };
264 CKB_SUCCESS
265}
266
267#[unsafe(no_mangle)]
268pub extern "C" fn ckb_load_header_by_field(
269 ptr: *mut c_void,
270 len: *mut u64,
271 offset: u64,
272 index: u64,
273 source: u64,
274 field: u64,
275) -> c_int {
276 let header = match fetch_header(index, source) {
277 Ok(input) => input,
278 Err(code) => return code,
279 };
280 let epoch = header.epoch();
281 let value = match field {
282 HEADER_FIELD_EPOCH_NUMBER => epoch.number(),
283 HEADER_FIELD_EPOCH_START_BLOCK_NUMBER => header.number().checked_sub(epoch.index()).expect("Overflow!"),
284 HEADER_FIELD_EPOCH_LENGTH => epoch.length(),
285 _ => panic!("Invalid field: {}", field),
286 };
287 let data = value.to_le_bytes();
288 store_data(ptr, len, offset, &data[..]);
289 CKB_SUCCESS
290}
291
292#[unsafe(no_mangle)]
293pub extern "C" fn ckb_load_input_by_field(
294 ptr: *mut c_void,
295 len: *mut u64,
296 offset: u64,
297 index: u64,
298 source: u64,
299 field: u64,
300) -> c_int {
301 let input = match fetch_input(index, source) {
302 Ok(input) => input,
303 Err(code) => return code,
304 };
305 match field {
306 INPUT_FIELD_OUT_POINT => {
307 store_data(ptr, len, offset, input.previous_output().as_slice());
308 }
309 INPUT_FIELD_SINCE => {
310 let since: u64 = input.since().unpack();
311 let data = since.to_le_bytes();
312 store_data(ptr, len, offset, &data[..]);
313 }
314 _ => panic!("Invalid field: {}", field),
315 };
316 CKB_SUCCESS
317}
318
319#[unsafe(no_mangle)]
320pub extern "C" fn ckb_load_cell_data(ptr: *mut c_void, len: *mut u64, offset: u64, index: u64, source: u64) -> c_int {
321 let (_, cell_data) = match fetch_cell(index, source) {
322 Ok(cell) => cell,
323 Err(code) => return code,
324 };
325 store_data(ptr, len, offset, &cell_data);
326 CKB_SUCCESS
327}
328
329unsafe extern "C" {
330 unsafe fn simulator_internal_dlopen2(
331 native_library_path: *const u8,
332 code: *const u8,
333 length: u64,
334 aligned_addr: *mut u8,
335 aligned_size: u64,
336 handle: *mut *mut c_void,
337 consumed_size: *mut u64,
338 ) -> c_int;
339}
340
341fn rs_simulator_internal_dlopen2(
343 native_library_path: *const u8,
344 code: *const u8,
345 length: u64,
346 aligned_addr: *mut u8,
347 aligned_size: u64,
348 handle: *mut *mut c_void,
349 consumed_size: *mut u64,
350) -> c_int {
351 unsafe {
352 simulator_internal_dlopen2(native_library_path, code, length, aligned_addr, aligned_size, handle, consumed_size)
353 }
354}
355
356#[unsafe(no_mangle)]
357pub extern "C" fn ckb_dlopen2(
358 dep_cell_hash: *const u8,
359 hash_type: u8,
360 aligned_addr: *mut u8,
361 aligned_size: u64,
362 handle: *mut *mut c_void,
363 consumed_size: *mut u64,
364) -> c_int {
365 let dep_cell_hash = utils::to_array(dep_cell_hash, 32);
366 let mut buffer = vec![];
367 buffer.extend_from_slice(dep_cell_hash);
368 buffer.push(hash_type);
369 let key = format!("0x{}", faster_hex::hex_string(&buffer));
370 let filename = SETUP.native_binaries.get(&key).expect("cannot locate native binary!");
371 let cell_dep = TRANSACTION
372 .mock_info
373 .cell_deps
374 .iter()
375 .find(|cell_dep| {
376 if hash_type == 1 {
377 cell_dep
378 .output
379 .type_()
380 .to_opt()
381 .map(|t| t.calc_script_hash().as_slice() == dep_cell_hash)
382 .unwrap_or(false)
383 } else {
384 CellOutput::calc_data_hash(&cell_dep.data).as_slice() == dep_cell_hash
385 }
386 })
387 .expect("cannot locate cell dep");
388 let cell_data = cell_dep.data.as_ref();
389 rs_simulator_internal_dlopen2(
390 filename.as_str().as_ptr(),
391 cell_data.as_ptr(),
392 cell_data.len() as u64,
393 aligned_addr,
394 aligned_size,
395 handle,
396 consumed_size,
397 )
398}
399
400#[unsafe(no_mangle)]
401pub extern "C" fn set_script_info(ptr: *const std::ffi::c_void, tx_ctx_id: u64, proc_ctx_id: u64) {
402 if ptr.is_null() && tx_ctx_id == 0 && proc_ctx_id == 0 {
403 GlobalData::clean();
404 } else {
405 GlobalData::set_ptr(ptr);
406 SimContext::update_ctx_id(tx_ctx_id.into(), Some(proc_ctx_id.into()));
407 }
408}
409
410fn fetch_cell(index: u64, source: u64) -> Result<(CellOutput, Bytes), c_int> {
411 match source {
412 SOURCE_INPUT => TRANSACTION
413 .mock_info
414 .inputs
415 .get(index as usize)
416 .ok_or(CKB_INDEX_OUT_OF_BOUND)
417 .map(|input| (input.output.clone(), input.data.clone())),
418 SOURCE_OUTPUT => {
419 TRANSACTION.tx.raw().outputs().get(index as usize).ok_or(CKB_INDEX_OUT_OF_BOUND).map(|output| {
420 (output, TRANSACTION.tx.raw().outputs_data().get(index as usize).expect("cell data mismatch").unpack())
421 })
422 }
423 SOURCE_CELL_DEP => TRANSACTION
424 .mock_info
425 .cell_deps
426 .get(index as usize)
427 .ok_or(CKB_INDEX_OUT_OF_BOUND)
428 .map(|cell_dep| (cell_dep.output.clone(), cell_dep.data.clone())),
429 SOURCE_HEADER_DEP => Err(CKB_INDEX_OUT_OF_BOUND),
430 SOURCE_GROUP_INPUT => {
431 let (indices, _) = fetch_group_indices();
432 indices.get(index as usize).ok_or(CKB_INDEX_OUT_OF_BOUND).and_then(|actual_index| {
433 TRANSACTION
434 .mock_info
435 .inputs
436 .get(*actual_index)
437 .ok_or(CKB_INDEX_OUT_OF_BOUND)
438 .map(|input| (input.output.clone(), input.data.clone()))
439 })
440 }
441 SOURCE_GROUP_OUTPUT => {
442 let (_, indices) = fetch_group_indices();
443 indices.get(index as usize).ok_or(CKB_INDEX_OUT_OF_BOUND).and_then(|actual_index| {
444 TRANSACTION.tx.raw().outputs().get(*actual_index).ok_or(CKB_INDEX_OUT_OF_BOUND).map(|output| {
445 (
446 output,
447 TRANSACTION.tx.raw().outputs_data().get(*actual_index).expect("cell data mismatch").unpack(),
448 )
449 })
450 })
451 }
452 SOURCE_GROUP_CELL_DEP => Err(CKB_INDEX_OUT_OF_BOUND),
453 SOURCE_GROUP_HEADER_DEP => Err(CKB_INDEX_OUT_OF_BOUND),
454 _ => panic!("Invalid source: {}", source),
455 }
456}
457
458fn fetch_input(index: u64, source: u64) -> Result<CellInput, c_int> {
459 match source {
460 SOURCE_INPUT => TRANSACTION.tx.raw().inputs().get(index as usize).ok_or(CKB_INDEX_OUT_OF_BOUND),
461 SOURCE_OUTPUT => Err(CKB_INDEX_OUT_OF_BOUND),
462 SOURCE_CELL_DEP => Err(CKB_INDEX_OUT_OF_BOUND),
463 SOURCE_HEADER_DEP => Err(CKB_INDEX_OUT_OF_BOUND),
464 SOURCE_GROUP_INPUT => {
465 let (indices, _) = fetch_group_indices();
466 indices
467 .get(index as usize)
468 .ok_or(CKB_INDEX_OUT_OF_BOUND)
469 .and_then(|actual_index| TRANSACTION.tx.raw().inputs().get(*actual_index).ok_or(CKB_INDEX_OUT_OF_BOUND))
470 }
471 SOURCE_GROUP_OUTPUT => Err(CKB_INDEX_OUT_OF_BOUND),
472 SOURCE_GROUP_CELL_DEP => Err(CKB_INDEX_OUT_OF_BOUND),
473 SOURCE_GROUP_HEADER_DEP => Err(CKB_INDEX_OUT_OF_BOUND),
474 _ => panic!("Invalid source: {}", source),
475 }
476}
477
478fn find_header(hash: Byte32) -> Option<HeaderView> {
479 TRANSACTION.mock_info.header_deps.iter().find(|header| header.hash() == hash).cloned()
480}
481
482fn fetch_header(index: u64, source: u64) -> Result<HeaderView, c_int> {
483 match source {
484 SOURCE_INPUT => TRANSACTION
485 .mock_info
486 .inputs
487 .get(index as usize)
488 .and_then(|input| input.header.as_ref().cloned())
489 .ok_or(CKB_INDEX_OUT_OF_BOUND)
490 .and_then(|header_hash| find_header(header_hash).ok_or(CKB_ITEM_MISSING)),
491 SOURCE_OUTPUT => Err(CKB_INDEX_OUT_OF_BOUND),
492 SOURCE_CELL_DEP => TRANSACTION
493 .mock_info
494 .cell_deps
495 .get(index as usize)
496 .and_then(|cell_dep| cell_dep.header.as_ref().cloned())
497 .ok_or(CKB_INDEX_OUT_OF_BOUND)
498 .and_then(|header_hash| find_header(header_hash).ok_or(CKB_ITEM_MISSING)),
499 SOURCE_HEADER_DEP => {
500 TRANSACTION.mock_info.header_deps.get(index as usize).cloned().ok_or(CKB_INDEX_OUT_OF_BOUND)
501 }
502 SOURCE_GROUP_INPUT => {
503 let (indices, _) = fetch_group_indices();
504 indices.get(index as usize).ok_or(CKB_INDEX_OUT_OF_BOUND).and_then(|actual_index| {
505 TRANSACTION
506 .mock_info
507 .inputs
508 .get(*actual_index)
509 .and_then(|input| input.header.as_ref().cloned())
510 .ok_or(CKB_INDEX_OUT_OF_BOUND)
511 .and_then(|header_hash| find_header(header_hash).ok_or(CKB_ITEM_MISSING))
512 })
513 }
514 SOURCE_GROUP_OUTPUT => Err(CKB_INDEX_OUT_OF_BOUND),
515 SOURCE_GROUP_CELL_DEP => Err(CKB_INDEX_OUT_OF_BOUND),
516 SOURCE_GROUP_HEADER_DEP => Err(CKB_INDEX_OUT_OF_BOUND),
517 _ => panic!("Invalid source: {}", source),
518 }
519}
520
521fn fetch_witness(index: u64, source: u64) -> Option<packed::Bytes> {
522 match source {
523 SOURCE_INPUT => TRANSACTION.tx.witnesses().get(index as usize),
524 SOURCE_OUTPUT => TRANSACTION.tx.witnesses().get(index as usize),
525 SOURCE_GROUP_INPUT => {
526 let (indices, _) = fetch_group_indices();
527 indices.get(index as usize).and_then(|actual_index| TRANSACTION.tx.witnesses().get(*actual_index))
528 }
529 SOURCE_GROUP_OUTPUT => {
530 let (_, indices) = fetch_group_indices();
531 indices.get(index as usize).and_then(|actual_index| TRANSACTION.tx.witnesses().get(*actual_index))
532 }
533 SOURCE_CELL_DEP => None,
534 SOURCE_HEADER_DEP => None,
535 SOURCE_GROUP_CELL_DEP => None,
536 SOURCE_GROUP_HEADER_DEP => None,
537 _ => panic!("Invalid source: {}", source),
538 }
539}
540
541fn fetch_group_indices() -> (Vec<usize>, Vec<usize>) {
542 let mut input_indices: Vec<usize> = vec![];
543 let mut output_indices: Vec<usize> = vec![];
544 let current_script = fetch_current_script();
545
546 for (i, input) in TRANSACTION.mock_info.inputs.iter().enumerate() {
547 if SETUP.is_lock_script {
548 if input.output.lock() == current_script {
549 input_indices.push(i);
550 }
551 } else if let Some(t) = input.output.type_().to_opt() {
552 if t == current_script {
553 input_indices.push(i);
554 }
555 }
556 }
557 for (i, output) in TRANSACTION.tx.raw().outputs().into_iter().enumerate() {
558 if let Some(t) = output.type_().to_opt() {
559 if t == current_script {
560 output_indices.push(i);
561 }
562 }
563 }
564 (input_indices, output_indices)
565}
566
567fn fetch_current_script() -> Script {
568 let cell = if SETUP.is_output {
569 TRANSACTION.tx.raw().outputs().get(SETUP.script_index as usize).expect("running script index out of bound!")
570 } else {
571 TRANSACTION
572 .mock_info
573 .inputs
574 .get(SETUP.script_index as usize)
575 .expect("running script index out of bound!")
576 .output
577 .clone()
578 };
579 if SETUP.is_lock_script { cell.lock() } else { cell.type_().to_opt().unwrap() }
580}
581
582fn store_data(ptr: *mut c_void, len: *mut u64, offset: u64, data: &[u8]) {
583 let size_ptr = unsafe { len.as_mut().expect("casting pointer") };
584 let size = *size_ptr;
585 let buffer = unsafe { std::slice::from_raw_parts_mut(ptr as *mut u8, size as usize) };
586 let data_len = data.len() as u64;
587 let offset = std::cmp::min(data_len, offset);
588 let full_size = data_len - offset;
589 let real_size = std::cmp::min(size, full_size);
590 *size_ptr = full_size;
591 buffer[..real_size as usize].copy_from_slice(&data[offset as usize..(offset + real_size) as usize]);
592}