Skip to content

Commit 5bfe24d

Browse files
committed
feat(witness): add witness size, require_witness_size_at_least, and WitnessArgs Molecule field parsing
- Add WitnessMalformed(42) and WitnessFieldTruncated(43) runtime errors - Add witness::size (U64) and ckb::require_witness_size_at_least type signatures - Add LSP completions for witness size - Add IR lowering for __ckb_witness_size and __ckb_require_witness_size_at_least - Implement full codegen: witness size, size requirement check, raw witness loading, and WitnessArgs Molecule field extraction (lock/input_type/output_type) with proper bounds checking and error handling
1 parent 4deec06 commit 5bfe24d

5 files changed

Lines changed: 388 additions & 3 deletions

File tree

‎src/codegen/mod.rs‎

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ fn is_v014_runtime_helper(func: &str) -> bool {
236236
| "__ckb_witness_lock"
237237
| "__ckb_witness_input_type"
238238
| "__ckb_witness_output_type"
239+
| "__ckb_witness_size"
240+
| "__ckb_require_witness_size_at_least"
239241
| "__ckb_sighash_all"
240242
| "__ckb_require_maturity"
241243
| "__ckb_require_time"
@@ -9723,6 +9725,8 @@ impl CodeGenerator {
97239725
("__ckb_witness_lock", "WitnessArgs.lock"),
97249726
("__ckb_witness_input_type", "WitnessArgs.input_type"),
97259727
("__ckb_witness_output_type", "WitnessArgs.output_type"),
9728+
("__ckb_witness_size", "witness byte size"),
9729+
("__ckb_require_witness_size_at_least", "require witness size lower bound"),
97269730
("__ckb_sighash_all", "CKB sighash-all digest"),
97279731
("__ckb_require_maturity", "CKB block-number since maturity"),
97289732
("__ckb_require_time", "CKB timestamp since"),
@@ -9842,6 +9846,14 @@ impl CodeGenerator {
98429846
"__xudt_require_group_amount_burned" => {
98439847
self.emit_runtime_xudt_require_group_amount_delta_helper(name, false, enabled);
98449848
}
9849+
"__ckb_witness_size" => self.emit_runtime_witness_size_helper(enabled),
9850+
"__ckb_require_witness_size_at_least" => {
9851+
self.emit_runtime_require_witness_size_at_least_helper(enabled)
9852+
}
9853+
"__ckb_witness_raw" => self.emit_runtime_witness_raw_helper(enabled),
9854+
"__ckb_witness_lock" => self.emit_runtime_witness_args_field_helper(name, detail, 0, enabled),
9855+
"__ckb_witness_input_type" => self.emit_runtime_witness_args_field_helper(name, detail, 1, enabled),
9856+
"__ckb_witness_output_type" => self.emit_runtime_witness_args_field_helper(name, detail, 2, enabled),
98459857
_ => {
98469858
self.emit_global(name);
98479859
self.emit_label(name);
@@ -9857,6 +9869,345 @@ impl CodeGenerator {
98579869
}
98589870
}
98599871

9872+
fn emit_runtime_witness_size_helper(&mut self, enabled: bool) {
9873+
const SIZE_OFFSET: usize = 8;
9874+
const RA_OFFSET: usize = 24;
9875+
const FRAME_SIZE: usize = 32;
9876+
9877+
self.emit_global("__ckb_witness_size");
9878+
self.emit_label("__ckb_witness_size");
9879+
self.emit("# cellscript abi: witness byte size via LOAD_WITNESS");
9880+
self.emit("# cellscript abi: args a0=SourceView; returns a0=status, a1=size");
9881+
if !enabled {
9882+
self.emit(format!("li a0, {}", CellScriptRuntimeError::SyscallFailed.code()));
9883+
self.emit("ret");
9884+
return;
9885+
}
9886+
let invalid = self.fresh_label("witness_size_source_invalid");
9887+
let failed = self.fresh_label("witness_size_load_failed");
9888+
let done = self.fresh_label("witness_size_done");
9889+
let abi = self.runtime_abi();
9890+
9891+
self.emit(format!("addi sp, sp, -{}", FRAME_SIZE));
9892+
self.emit(format!("sd ra, {}(sp)", RA_OFFSET));
9893+
self.emit_decode_source_view_to_t1_t2(&invalid);
9894+
self.emit("li t0, 0");
9895+
self.emit(format!("sd t0, {}(sp)", SIZE_OFFSET));
9896+
self.emit("li a0, 0");
9897+
self.emit(format!("addi a1, sp, {}", SIZE_OFFSET));
9898+
self.emit("li a2, 0");
9899+
self.emit("addi a3, t1, 0");
9900+
self.emit("addi a4, t2, 0");
9901+
self.emit(format!("li a7, {}", abi.load_witness));
9902+
self.emit("ecall");
9903+
self.emit(format!("beqz a0, {}", done));
9904+
self.emit(format!("j {}", failed));
9905+
9906+
self.emit_label(&invalid);
9907+
self.emit(format!("li a0, {}", CellScriptRuntimeError::CkbSourceViewInvalid.code()));
9908+
self.emit(format!("ld ra, {}(sp)", RA_OFFSET));
9909+
self.emit(format!("addi sp, sp, {}", FRAME_SIZE));
9910+
self.emit("ret");
9911+
9912+
self.emit_label(&failed);
9913+
self.emit(format!("li a0, {}", CellScriptRuntimeError::SyscallFailed.code()));
9914+
self.emit(format!("ld ra, {}(sp)", RA_OFFSET));
9915+
self.emit(format!("addi sp, sp, {}", FRAME_SIZE));
9916+
self.emit("ret");
9917+
9918+
self.emit_label(&done);
9919+
self.emit("li a0, 0");
9920+
self.emit(format!("ld a1, {}(sp)", SIZE_OFFSET));
9921+
self.emit(format!("ld ra, {}(sp)", RA_OFFSET));
9922+
self.emit(format!("addi sp, sp, {}", FRAME_SIZE));
9923+
self.emit("ret");
9924+
}
9925+
9926+
fn emit_runtime_require_witness_size_at_least_helper(&mut self, enabled: bool) {
9927+
const SIZE_OFFSET: usize = 8;
9928+
const RA_OFFSET: usize = 24;
9929+
const FRAME_SIZE: usize = 32;
9930+
9931+
self.emit_global("__ckb_require_witness_size_at_least");
9932+
self.emit_label("__ckb_require_witness_size_at_least");
9933+
self.emit("# cellscript abi: require witness size >= min_size");
9934+
self.emit("# cellscript abi: args a0=SourceView, a1=min_size; returns a0=status");
9935+
if !enabled {
9936+
self.emit(format!("li a0, {}", CellScriptRuntimeError::SyscallFailed.code()));
9937+
self.emit("ret");
9938+
return;
9939+
}
9940+
let invalid = self.fresh_label("witness_req_size_source_invalid");
9941+
let failed = self.fresh_label("witness_req_size_load_failed");
9942+
let too_small = self.fresh_label("witness_req_size_too_small");
9943+
let ok = self.fresh_label("witness_req_size_ok");
9944+
let done = self.fresh_label("witness_req_size_done");
9945+
let abi = self.runtime_abi();
9946+
9947+
self.emit(format!("addi sp, sp, -{}", FRAME_SIZE));
9948+
self.emit(format!("sd ra, {}(sp)", RA_OFFSET));
9949+
self.emit_decode_source_view_to_t1_t2(&invalid);
9950+
self.emit("li t0, 0");
9951+
self.emit(format!("sd t0, {}(sp)", SIZE_OFFSET));
9952+
self.emit("li a0, 0");
9953+
self.emit(format!("addi a1, sp, {}", SIZE_OFFSET));
9954+
self.emit("li a2, 0");
9955+
self.emit("addi a3, t1, 0");
9956+
self.emit("addi a4, t2, 0");
9957+
self.emit(format!("li a7, {}", abi.load_witness));
9958+
self.emit("ecall");
9959+
self.emit(format!("beqz a0, {}", ok));
9960+
self.emit(format!("j {}", failed));
9961+
9962+
self.emit_label(&ok);
9963+
self.emit(format!("ld t0, {}(sp)", SIZE_OFFSET));
9964+
self.emit("addi t1, a1, 0");
9965+
self.emit("sltu t2, t0, t1");
9966+
self.emit(format!("beqz t2, {}", done));
9967+
9968+
self.emit_label(&too_small);
9969+
self.emit_epilogue();
9970+
9971+
self.emit_label(&invalid);
9972+
self.emit(format!("li a0, {}", CellScriptRuntimeError::CkbSourceViewInvalid.code()));
9973+
self.emit("ret");
9974+
9975+
self.emit_label(&failed);
9976+
self.emit(format!("li a0, {}", CellScriptRuntimeError::SyscallFailed.code()));
9977+
self.emit("ret");
9978+
9979+
self.emit_label(&done);
9980+
self.emit("li a0, 0");
9981+
self.emit(format!("ld ra, {}(sp)", RA_OFFSET));
9982+
self.emit(format!("addi sp, sp, {}", FRAME_SIZE));
9983+
self.emit("ret");
9984+
}
9985+
9986+
fn emit_runtime_witness_raw_helper(&mut self, enabled: bool) {
9987+
const SIZE_OFFSET: usize = 8;
9988+
const BUFFER_OFFSET: usize = 16;
9989+
const RA_OFFSET: usize = 56;
9990+
const FRAME_SIZE: usize = 64;
9991+
9992+
self.emit_global("__ckb_witness_raw");
9993+
self.emit_label("__ckb_witness_raw");
9994+
self.emit("# cellscript abi: load raw witness bytes as a 32-byte Hash");
9995+
self.emit("# cellscript abi: args a0=out32_ptr, a1=size_ptr; returns a0=status");
9996+
if !enabled {
9997+
self.emit(format!("li a0, {}", CellScriptRuntimeError::SyscallFailed.code()));
9998+
self.emit("ret");
9999+
return;
10000+
}
10001+
let invalid = self.fresh_label("witness_raw_source_invalid");
10002+
let failed = self.fresh_label("witness_raw_load_failed");
10003+
let done = self.fresh_label("witness_raw_done");
10004+
let abi = self.runtime_abi();
10005+
10006+
self.emit(format!("addi sp, sp, -{}", FRAME_SIZE));
10007+
self.emit(format!("sd ra, {}(sp)", RA_OFFSET));
10008+
self.emit_decode_source_view_to_t1_t2(&invalid);
10009+
self.emit("li t0, 32");
10010+
self.emit(format!("sd t0, {}(sp)", SIZE_OFFSET));
10011+
self.emit(format!("addi a0, sp, {}", BUFFER_OFFSET));
10012+
self.emit(format!("addi a1, sp, {}", SIZE_OFFSET));
10013+
self.emit("li a2, 0");
10014+
self.emit("addi a3, t1, 0");
10015+
self.emit("addi a4, t2, 0");
10016+
self.emit(format!("li a7, {}", abi.load_witness));
10017+
self.emit("ecall");
10018+
self.emit(format!("beqz a0, {}", done));
10019+
self.emit(format!("j {}", failed));
10020+
10021+
self.emit_label(&invalid);
10022+
self.emit(format!("li a0, {}", CellScriptRuntimeError::CkbSourceViewInvalid.code()));
10023+
self.emit("ret");
10024+
10025+
self.emit_label(&failed);
10026+
self.emit(format!("li a0, {}", CellScriptRuntimeError::SyscallFailed.code()));
10027+
self.emit("ret");
10028+
10029+
self.emit_label(&done);
10030+
self.emit("li a0, 0");
10031+
self.emit(format!("ld ra, {}(sp)", RA_OFFSET));
10032+
self.emit(format!("addi sp, sp, {}", FRAME_SIZE));
10033+
self.emit("ret");
10034+
}
10035+
10036+
fn emit_runtime_witness_args_field_helper(&mut self, symbol: &str, detail: &str, field_index: u64, enabled: bool) {
10037+
const SIZE_OFFSET: usize = 8;
10038+
const FULL_BUFFER_OFFSET: usize = 16;
10039+
const FULL_BUFFER_SIZE: usize = 512;
10040+
const FIELD_SIZE_OFFSET: usize = SIZE_OFFSET + FULL_BUFFER_SIZE;
10041+
const FIELD_BUF_OFFSET: usize = FIELD_SIZE_OFFSET + 8;
10042+
const FIELD_BUF_SIZE: usize = 128;
10043+
const HEADER_READ_OFFSET: usize = FIELD_BUF_OFFSET + FIELD_BUF_SIZE;
10044+
const RA_OFFSET: usize = HEADER_READ_OFFSET + 8;
10045+
const FRAME_SIZE: usize = RA_OFFSET + 8;
10046+
10047+
self.emit_global(symbol);
10048+
self.emit_label(symbol);
10049+
self.emit(format!("# cellscript abi: extract WitnessArgs field {} ({})", field_index, detail));
10050+
self.emit("# cellscript abi: args a0=out32_ptr, a1=size_ptr; returns a0=status");
10051+
if !enabled {
10052+
self.emit(format!("li a0, {}", CellScriptRuntimeError::SyscallFailed.code()));
10053+
self.emit("ret");
10054+
return;
10055+
}
10056+
let invalid = self.fresh_label("witness_field_source_invalid");
10057+
let failed = self.fresh_label("witness_field_load_failed");
10058+
let malformed = self.fresh_label("witness_field_malformed");
10059+
let truncated = self.fresh_label("witness_field_truncated");
10060+
let field_absent = self.fresh_label("witness_field_absent");
10061+
let ok = self.fresh_label("witness_field_ok");
10062+
let done = self.fresh_label("witness_field_done");
10063+
let abi = self.runtime_abi();
10064+
10065+
self.emit(format!("addi sp, sp, -{}", FRAME_SIZE));
10066+
self.emit(format!("sd ra, {}(sp)", RA_OFFSET));
10067+
self.emit_decode_source_view_to_t1_t2(&invalid);
10068+
10069+
// Load full witness
10070+
self.emit(format!("li t0, {}", FULL_BUFFER_SIZE));
10071+
self.emit(format!("sd t0, {}(sp)", SIZE_OFFSET));
10072+
self.emit(format!("addi a0, sp, {}", FULL_BUFFER_OFFSET));
10073+
self.emit(format!("addi a1, sp, {}", SIZE_OFFSET));
10074+
self.emit("li a2, 0");
10075+
self.emit("addi a3, t1, 0");
10076+
self.emit("addi a4, t2, 0");
10077+
self.emit(format!("li a7, {}", abi.load_witness));
10078+
self.emit("ecall");
10079+
self.emit(format!("beqz a0, {}", ok));
10080+
self.emit(format!("j {}", failed));
10081+
10082+
self.emit_label(&ok);
10083+
self.emit(format!("ld t0, {}(sp)", SIZE_OFFSET));
10084+
10085+
// Parse Molecule WitnessArgs table header (20 bytes minimum)
10086+
self.emit("li t1, 20");
10087+
self.emit("sltu t2, t0, t1");
10088+
self.emit(format!("bnez t2, {}", malformed));
10089+
10090+
// Read field_count at offset 4
10091+
self.emit(format!("addi t3, sp, {}", FULL_BUFFER_OFFSET));
10092+
self.emit("lwu t4, 4(t3)");
10093+
self.emit("li t5, 3");
10094+
self.emit("sub t6, t4, t5");
10095+
self.emit(format!("bnez t6, {}", malformed));
10096+
10097+
// Read field offsets from header (offsets 8, 12, 16)
10098+
self.emit("lwu t4, 8(t3)");
10099+
self.emit(format!("sd t4, {}(sp)", HEADER_READ_OFFSET));
10100+
self.emit("lwu t5, 12(t3)");
10101+
self.emit(format!("sd t5, {}(sp)", HEADER_READ_OFFSET + 8));
10102+
self.emit("lwu t6, 16(t3)");
10103+
self.emit(format!("sd t6, {}(sp)", HEADER_READ_OFFSET + 16));
10104+
10105+
// Select field offset and next field offset
10106+
let field_offsets_offset = HEADER_READ_OFFSET + (field_index * 8) as usize;
10107+
let next_offsets_offset = HEADER_READ_OFFSET + ((field_index + 1) * 8) as usize;
10108+
self.emit(format!("ld t4, {}(sp)", field_offsets_offset));
10109+
if field_index < 2 {
10110+
self.emit(format!("ld t5, {}(sp)", next_offsets_offset));
10111+
} else {
10112+
self.emit(format!("addi t5, t0, 0"));
10113+
}
10114+
10115+
// Check field offset bounds: field_offset < next_offset <= total_size
10116+
self.emit("sltu t2, t4, t5");
10117+
self.emit(format!("beqz t2, {}", malformed));
10118+
self.emit("sltu t2, t0, t5");
10119+
self.emit(format!("bnez t2, {}", truncated));
10120+
10121+
// Calculate field data size: next_offset - field_offset - 4 (BytesOpt length prefix)
10122+
self.emit("sub t2, t5, t4");
10123+
self.emit("li t6, 4");
10124+
self.emit("sltu t3, t2, t6");
10125+
self.emit(format!("bnez t3, {}", malformed));
10126+
self.emit("addi t2, t2, -4");
10127+
10128+
// Read BytesOpt length at field_offset
10129+
self.emit(format!("addi t3, sp, {}", FULL_BUFFER_OFFSET));
10130+
self.emit("add t6, t3, t4");
10131+
self.emit("lwu t1, 0(t6)");
10132+
self.emit(format!("beqz t1, {}", field_absent));
10133+
10134+
// Check length <= field data size
10135+
self.emit("sltu t3, t2, t1");
10136+
self.emit(format!("bnez t3, {}", malformed));
10137+
10138+
// Copy field bytes to output buffer (max 32 bytes for Hash)
10139+
self.emit("li t3, 32");
10140+
self.emit("sltu t5, t3, t1");
10141+
self.emit("bnez t5, 1f");
10142+
self.emit("addi t1, t3, 0");
10143+
self.emit("1:");
10144+
self.emit(format!("addi t2, sp, {}", FIELD_BUF_OFFSET));
10145+
self.emit("addi t4, t6, 4");
10146+
// Copy loop
10147+
self.emit("li t3, 0");
10148+
let copy_loop = self.fresh_label("witness_field_copy_loop");
10149+
let copy_done = self.fresh_label("witness_field_copy_done");
10150+
self.emit_label(&copy_loop);
10151+
self.emit("sltu t5, t3, t1");
10152+
self.emit(format!("beqz t5, {}", copy_done));
10153+
self.emit("add t5, t4, t3");
10154+
self.emit("lbu t6, 0(t5)");
10155+
self.emit("add t5, t2, t3");
10156+
self.emit("sb t6, 0(t5)");
10157+
self.emit("addi t3, t3, 1");
10158+
self.emit(format!("j {}", copy_loop));
10159+
self.emit_label(&copy_done);
10160+
self.emit(format!("j {}", done));
10161+
10162+
self.emit_label(&field_absent);
10163+
self.emit("li t1, 0");
10164+
self.emit("li t3, 32");
10165+
self.emit(format!("addi t2, sp, {}", FIELD_BUF_OFFSET));
10166+
// Zero-fill for absent field
10167+
self.emit("li t4, 0");
10168+
let zero_loop = self.fresh_label("witness_field_zero_loop");
10169+
let zero_done = self.fresh_label("witness_field_zero_done");
10170+
self.emit_label(&zero_loop);
10171+
self.emit("sltu t5, t4, t3");
10172+
self.emit(format!("beqz t5, {}", zero_done));
10173+
self.emit("add t5, t2, t4");
10174+
self.emit("sb zero, 0(t5)");
10175+
self.emit("addi t4, t4, 1");
10176+
self.emit(format!("j {}", zero_loop));
10177+
self.emit_label(&zero_done);
10178+
self.emit(format!("j {}", done));
10179+
10180+
self.emit_label(&malformed);
10181+
self.emit(format!("li a0, {}", CellScriptRuntimeError::WitnessMalformed.code()));
10182+
self.emit(format!("ld ra, {}(sp)", RA_OFFSET));
10183+
self.emit(format!("addi sp, sp, {}", FRAME_SIZE));
10184+
self.emit("ret");
10185+
10186+
self.emit_label(&truncated);
10187+
self.emit(format!("li a0, {}", CellScriptRuntimeError::WitnessFieldTruncated.code()));
10188+
self.emit(format!("ld ra, {}(sp)", RA_OFFSET));
10189+
self.emit(format!("addi sp, sp, {}", FRAME_SIZE));
10190+
self.emit("ret");
10191+
10192+
self.emit_label(&invalid);
10193+
self.emit(format!("li a0, {}", CellScriptRuntimeError::CkbSourceViewInvalid.code()));
10194+
self.emit(format!("ld ra, {}(sp)", RA_OFFSET));
10195+
self.emit(format!("addi sp, sp, {}", FRAME_SIZE));
10196+
self.emit("ret");
10197+
10198+
self.emit_label(&failed);
10199+
self.emit(format!("li a0, {}", CellScriptRuntimeError::SyscallFailed.code()));
10200+
self.emit(format!("ld ra, {}(sp)", RA_OFFSET));
10201+
self.emit(format!("addi sp, sp, {}", FRAME_SIZE));
10202+
self.emit("ret");
10203+
10204+
self.emit_label(&done);
10205+
self.emit("li a0, 0");
10206+
self.emit(format!("ld ra, {}(sp)", RA_OFFSET));
10207+
self.emit(format!("addi sp, sp, {}", FRAME_SIZE));
10208+
self.emit("ret");
10209+
}
10210+
986010211
fn emit_runtime_current_script_hash_helper(&mut self, enabled: bool) {
986110212
self.emit_global("__ckb_current_script_hash");
986210213
self.emit_label("__ckb_current_script_hash");

‎src/ir/mod.rs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3715,6 +3715,18 @@ impl IrGenerator {
37153715
blocks,
37163716
vars,
37173717
),
3718+
"witness::size" if call.args.len() == 1 => self.lower_simple_runtime_call(
3719+
"__ckb_witness_size",
3720+
"witness_size",
3721+
IrType::U64,
3722+
&call.args,
3723+
current,
3724+
blocks,
3725+
vars,
3726+
),
3727+
"ckb::require_witness_size_at_least" if call.args.len() == 2 => {
3728+
self.lower_void_runtime_call("__ckb_require_witness_size_at_least", &call.args, current, blocks, vars)
3729+
}
37183730
"dao::accumulated_rate" if call.args.len() == 1 => self.lower_simple_runtime_call(
37193731
"__dao_accumulated_rate",
37203732
"dao_accumulated_rate",

0 commit comments

Comments
 (0)