diff --git a/codegen/src/asm.rs b/codegen/src/asm.rs index 0103011b1..088d8e6a9 100644 --- a/codegen/src/asm.rs +++ b/codegen/src/asm.rs @@ -117,7 +117,6 @@ impl Assembler { /// Mock the stack input and output for checking /// the stack usages. pub fn emit_op(&mut self, opcode: OpCode) -> Result<()> { - // tracing::trace!("stack length: {:?}", self.sp); tracing::trace!("emit opcode: {:?}", opcode); self.decrement_sp(opcode.stack_in() as u8)?; self.emit(opcode.into()); diff --git a/codegen/src/jump/mod.rs b/codegen/src/jump/mod.rs index c46899bd4..c65150440 100644 --- a/codegen/src/jump/mod.rs +++ b/codegen/src/jump/mod.rs @@ -1,6 +1,7 @@ //! Jump table implementation. use crate::codegen::ExtFunc; +use core::fmt::Display; pub use table::JumpTable; mod pc; @@ -21,6 +22,17 @@ pub enum Jump { ExtFunc(ExtFunc), } +impl Display for Jump { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Jump::Offset(offset) => write!(f, "Offset(0x{offset:x})"), + Jump::Label(offset) => write!(f, "Label(0x{offset:x})"), + Jump::Func(index) => write!(f, "Func({index})"), + Jump::ExtFunc(_) => write!(f, "ExtFunc"), + } + } +} + impl Jump { /// If the target is a label. pub fn is_label(&self) -> bool { @@ -31,4 +43,9 @@ impl Jump { pub fn is_offset(&self) -> bool { matches!(self, Jump::Offset(_)) } + + /// If the target is a function call + pub fn is_call(&self) -> bool { + !self.is_label() && !self.is_offset() + } } diff --git a/codegen/src/jump/pc.rs b/codegen/src/jump/pc.rs index 79d943068..72abd4a48 100644 --- a/codegen/src/jump/pc.rs +++ b/codegen/src/jump/pc.rs @@ -1,7 +1,7 @@ //! Program counter handlers. use crate::{ - jump::{relocate, Jump, JumpTable}, + jump::{Jump, JumpTable}, Error, Result, BUFFER_LIMIT, }; @@ -11,9 +11,44 @@ impl JumpTable { tracing::trace!("shift pc from 0x{start:x} with offset={offset}"); self.shift_label_pc(start, offset)?; self.shift_label_target(start, offset)?; - self.shift_func_target(start, offset)?; + self.shift_func_target(start, offset) + } - Ok(()) + /// Shift the target program counters. + /// + /// Calculating target pc from the offset of original pc. + pub fn shift_targets(&mut self) -> Result<()> { + let mut total_offset = 0; + self.jump + .clone() + .iter() + .try_for_each(|(original_pc, target)| -> Result<()> { + tracing::debug!("shift targets for {target} <- (0x{original_pc:x})"); + let pc = original_pc + total_offset; + let offset = self.target_offset( + target, + if target.is_offset() { + total_offset + original_pc + } else { + total_offset + }, + )?; + + self.shift_target(pc, offset)?; + total_offset += offset; + Ok(()) + }) + } + + /// Shift the program counter of targets with given ptr and offset. + /// + /// 1. shift code section. + /// 2. shift label targets. + /// 3. shift function targets. + pub fn shift_target(&mut self, ptr: u16, offset: u16) -> Result<()> { + self.code.shift(offset); + self.shift_label_target(ptr, offset)?; + self.shift_func_target(ptr, offset) } /// Shift program counter for labels. @@ -23,12 +58,14 @@ impl JumpTable { .iter() .map(|(k, v)| { let mut k = *k; + let next_label = k + offset; if k > start { tracing::trace!( - "shift {v:x?} pc with offset={offset}: 0x{k:x}(0x{start:x}) -> 0x{:x}", - k + offset + "shift {v} pc with offset={offset}: 0x{k:x}(0x{start:x}) -> 0x{:x}", + next_label ); - k += offset; + k = next_label; + if k > BUFFER_LIMIT as u16 { return Err(Error::InvalidPC(k as usize)); } @@ -41,63 +78,55 @@ impl JumpTable { Ok(()) } - /// Shift the target program counters. - /// - /// Calculating target pc from the offset of original pc. - pub fn shift_targets(&mut self) -> Result<()> { - let mut total_offset = 0; - self.jump - .clone() - .keys() - .try_for_each(|original_pc| -> Result<()> { - let pc = original_pc + total_offset; - let offset = relocate::offset(pc)?; - total_offset += offset; - self.shift_target(pc, offset) - }) - } + /// Shift program counter for functions. + pub fn shift_func_target(&mut self, ptr: u16, offset: u16) -> Result<()> { + if self.func.is_empty() { + tracing::trace!("No functions to shift."); + return Ok(()); + } - /// Shift the program counter of targets with given ptr and offset. - /// - /// 1. shift code section. - /// 2. shift label targets. - /// 3. shift function targets. - pub fn shift_target(&mut self, ptr: u16, offset: u16) -> Result<()> { - self.code.shift(offset); - self.shift_label_target(ptr, offset)?; - self.shift_func_target(ptr, offset) - } + self.func.iter_mut().try_for_each(|(index, target)| { + let next_target = *target + offset; - /// Shift program counter for functions. - pub fn shift_func_target(&mut self, start: u16, offset: u16) -> Result<()> { - self.func.iter_mut().try_for_each(|(k, v)| { - if *v > start { + if *target > ptr { tracing::trace!( - "shift Func({k}) target with offset={offset}: 0x{v:x}(0x{start:x}) -> 0x{:x}", - *v + offset + "shift Func({index}) target with offset={offset}: 0x{target:x}(0x{ptr:x}) -> 0x{:x}", + next_target + ); + + *target = next_target; + } else { + tracing::trace!( + "shift Func({index}) target with offset=0: 0x{target:x}(0x{ptr:x}) -> 0x{target:x}" ); - *v += offset; - if *v > BUFFER_LIMIT as u16 { - return Err(Error::InvalidPC(*v as usize)); - } } Ok(()) - })?; - - Ok(()) + }) } /// Shift target program counter for labels. pub fn shift_label_target(&mut self, ptr: u16, offset: u16) -> Result<()> { + if self.jump.is_empty() { + tracing::trace!("No labels to shift."); + return Ok(()); + } + self.jump.iter_mut().try_for_each(|(pc, target)| { if let Jump::Label(label) = target { + let next_label = *label + offset; + if *label > ptr { tracing::trace!( - "shift Label(pc=0x{pc:x}) target with offset={offset} 0x{label:x}(0x{ptr:x}) -> 0x{:x}", - *label + offset + "shift Label(0x{pc:x}) target with offset={offset}: 0x{label:x}(0x{ptr:x}) -> 0x{:x}", + next_label, + ); + + *label = next_label; + } else { + tracing::trace!( + "shift Label(0x{pc:x}) target with offset=0: 0x{label:x}(0x{ptr:x}) -> 0x{label:x}" ); - *label += offset; } } diff --git a/codegen/src/jump/relocate.rs b/codegen/src/jump/relocate.rs index 9bb08eec5..6fc16d365 100644 --- a/codegen/src/jump/relocate.rs +++ b/codegen/src/jump/relocate.rs @@ -1,7 +1,7 @@ //! Program Relocations use crate::{ - jump::{relocate, JumpTable}, + jump::{relocate, Jump, JumpTable}, wasm::ToLSBytes, Buffer, Error, Result, BUFFER_LIMIT, }; @@ -13,72 +13,98 @@ impl JumpTable { /// *WARNING*: This function should only be called once in the compiler. /// considering move it to the compiler. pub fn relocate(&mut self, buffer: &mut Buffer) -> Result<()> { + // pre-calculate and shift targets self.shift_targets()?; tracing::trace!("code section offset: 0x{:x}", self.code.offset()); + // relocate functions while let Some((pc, jump)) = self.jump.pop_first() { - tracing::trace!("run relocation for {jump:?}"); - - let offset = relocate::offset(pc)?; + tracing::debug!("run relocation for {jump}"); let mut target = self.target(&jump)?; - if jump.is_offset() { - target += pc; - } + self.relocate_offset(pc, jump, &mut target)?; - relocate::pc(buffer, pc, target, offset)?; - self.shift_label_pc(pc, offset)?; + let offset = relocate::pc(buffer, pc, target)?; + self.shift_label_pc(pc, offset as u16)?; } buffer.extend_from_slice(&self.code.finish()); Ok(()) } -} -/// Get the offset of the program counter for relocation. -pub fn offset(original_pc: u16) -> Result { - let pc = original_pc; - let mut offset = 0; - - // Update the target program counter - { - // The maximum size of the PC is 2 bytes, whatever PUSH1 or PUSH2 - // takes 1 more byte. - offset += 1; - - // Update the program counter for the edge cases. - // - // Start from 0xff, the lowest significant bytes of the target - // program counter will take 2 bytes instead of 1 byte. - // - // | PC | PC BYTES | TARGET PC | - // |------|----------|-----------| - // | 0xfe | 1 | 0xff | - // | 0xff | 2 | 0x101 | - offset += if pc > 0xfe { 2 } else { 1 } - } + /// relocate the target of offset jump + fn relocate_offset(&self, pc: u16, jump: Jump, target: &mut u16) -> Result<()> { + if !jump.is_offset() { + return Ok(()); + } - // Check PC range. - if pc + offset > BUFFER_LIMIT as u16 { - return Err(Error::InvalidPC((pc + offset) as usize)); - } + // NOTE: If the target is offset the return data is + // the offset instead of the PC. + *target += pc; + + // check the original pc of the offset is greater than 0xff + if pc > 0xff { + *target += 1; + } - Ok(offset) + // check if the offset of the embedded call is greater than 0xff + if let Some((_, next_target)) = self.jump.first_key_value() { + if next_target.is_call() && self.target(next_target)? > 0xff { + *target += 1 + } + } + + Ok(()) + } } +// /// Get the offset of the program counter for relocation. +// pub fn offset(pc: u16) -> Result { +// let mut offset = 0; +// +// // Update the target program counter +// { +// // The maximum size of the PC is 2 bytes, whatever PUSH1 or PUSH32 +// // takes 1 more byte. +// offset += 1; +// +// // Update the program counter for the edge cases. +// // +// // Start from 0xff, the lowest significant bytes of the target +// // program counter will take 2 bytes instead of 1 byte. +// // +// // | PC | PC BYTES | TARGET PC | +// // |------|----------|-----------| +// // | 0xfe | 1 | 0xff | +// // | 0xff | 2 | 0x0101 | +// offset += if pc > 0xfe { 2 } else { 1 } +// } +// +// // Check PC range. +// if pc + offset > BUFFER_LIMIT as u16 { +// return Err(Error::InvalidPC((pc + offset) as usize)); +// } +// +// Ok(offset) +// } + /// Relocate program counter to buffer. -fn pc(buffer: &mut Buffer, original_pc: u16, target_pc: u16, offset: u16) -> Result<()> { +fn pc(buffer: &mut Buffer, original_pc: u16, target_pc: u16) -> Result { let original_pc = original_pc as usize; let mut new_buffer: Buffer = buffer[..original_pc].into(); let rest_buffer: Buffer = buffer[original_pc..].into(); - if offset == 2 { + let pc_offset = target_pc.to_ls_bytes(); + if pc_offset.len() == 1 { new_buffer.push(OpCode::PUSH1.into()); } else { new_buffer.push(OpCode::PUSH2.into()); } - let pc_offset = target_pc.to_ls_bytes(); - tracing::trace!("push bytes: 0x{:x?} at 0x{:x}", pc_offset, original_pc); + tracing::trace!( + "push bytes: 0x{} at 0x{}", + hex::encode(&pc_offset), + hex::encode(original_pc.to_ls_bytes()) + ); new_buffer.extend_from_slice(&pc_offset); new_buffer.extend_from_slice(&rest_buffer); @@ -88,5 +114,5 @@ fn pc(buffer: &mut Buffer, original_pc: u16, target_pc: u16, offset: u16) -> Res } *buffer = new_buffer; - Ok(()) + Ok(1 + pc_offset.len()) } diff --git a/codegen/src/jump/table.rs b/codegen/src/jump/table.rs index af7d874bd..4b20ff1be 100644 --- a/codegen/src/jump/table.rs +++ b/codegen/src/jump/table.rs @@ -81,7 +81,7 @@ impl JumpTable { } /// Get the target of a jump. - pub fn target(&mut self, jump: &Jump) -> Result { + pub fn target(&self, jump: &Jump) -> Result { match jump { Jump::Offset(offset) => Ok(*offset), Jump::Label(label) => Ok(*label), @@ -89,4 +89,12 @@ impl JumpTable { Jump::ExtFunc(ext) => Ok(self.code.offset_of(ext).ok_or(Error::ExtFuncNotFound)?), } } + + /// Get the target offset + pub fn target_offset(&self, jump: &Jump, offset: u16) -> Result { + let target = self.target(jump)?; + + // [PUSH_N, PC] + Ok(if (target + offset) > 0xff { 3 } else { 2 }) + } } diff --git a/codegen/src/masm/ret.rs b/codegen/src/masm/ret.rs index ee1c9dd1c..84ef44ab2 100644 --- a/codegen/src/masm/ret.rs +++ b/codegen/src/masm/ret.rs @@ -34,6 +34,7 @@ impl MacroAssembler { let len = results.len() as u8; tracing::trace!("cleaning frame stack, target: {}", len + 1); + // TODO: clean stacks via the count of nested control stacks. while self.sp() > len + 1 { self._drop()?; } diff --git a/codegen/src/visitor/call.rs b/codegen/src/visitor/call.rs index b50b5f9e4..3d0a9ef8a 100644 --- a/codegen/src/visitor/call.rs +++ b/codegen/src/visitor/call.rs @@ -42,7 +42,7 @@ impl Function { .into()); } - tracing::trace!("call internal function: index={index}"); + tracing::debug!("call internal function: index={index}"); let (params, results) = self.env.funcs.get(&index).unwrap_or(&(0, 0)); // TODO: adapat the case that the params is larger than 0xff (#247) @@ -53,7 +53,7 @@ impl Function { // [ .., // , // params[SWAP], params[PUSH, SLOT, MSTORE], - // PUSH, PC, JUMP, + // {(PUSH, PC), JUMP, JUMPDEST} // ] // <- selfparams[PUSH, OFFSET, CALLDATALOAD] // diff --git a/codegen/src/visitor/local.rs b/codegen/src/visitor/local.rs index 3d8525dd4..63e71a4d3 100644 --- a/codegen/src/visitor/local.rs +++ b/codegen/src/visitor/local.rs @@ -6,7 +6,7 @@ impl Function { /// This instruction gets the value of a variable. pub fn _local_get(&mut self, local_index: u32) -> Result<()> { let local_index = local_index as usize; - if (self.is_main && local_index < self.ty.params().len()) || self.abi.is_some() { + if (self.is_main || self.abi.is_some()) && local_index < self.ty.params().len() { // Parsing data from selector. self._local_get_calldata(local_index) } else { diff --git a/compiler/src/parser.rs b/compiler/src/parser.rs index 7e4c05682..a8a2418a7 100644 --- a/compiler/src/parser.rs +++ b/compiler/src/parser.rs @@ -47,7 +47,8 @@ impl<'p> Parser<'p> { let locals = fun.body.get_locals_reader()?.get_count(); let params = sig.params().len(); tracing::trace!( - "computing slots for function {idx}, locals: {locals}, params: {params}, reserved: {slots}" + "computing slots for function {idx}, locals: {locals}, params: {params}, reserved: {slots}, external: {}", + self.env.is_external(fun.index()) ); self.env.slots.insert(fun.index(), slots); @@ -56,6 +57,8 @@ impl<'p> Parser<'p> { .insert(fun.index(), (params as u32, sig.results().len() as u32)); slots += locals; + + // process prarams for internal functions only if !self.env.is_external(fun.index()) && !self.env.is_main(fun.index()) { slots += params as u32; } diff --git a/examples/approval.rs b/examples/approval.rs index 914a10147..8965d7e6a 100644 --- a/examples/approval.rs +++ b/examples/approval.rs @@ -56,7 +56,11 @@ fn main() {} fn test_approval() -> anyhow::Result<()> { use zint::{Bytes32, Contract, EVM}; - let mut evm = EVM::default().commit(true).caller([1; 20]); + let caller_bytes = hex::decode("be862ad9abfe6f22bcb087716c7d89a26051f74c")?; + let mut caller = [0; 20]; + caller.copy_from_slice(&caller_bytes); + + let mut evm = EVM::default().commit(true).caller(caller); let contract = Contract::search("approval")?.compile()?; let info = evm.deploy(&contract.bytecode()?)?; let address = info.address; @@ -79,27 +83,30 @@ fn test_approval() -> anyhow::Result<()> { )?; assert_eq!(value.to_bytes32(), allowance); - // TODO: #273 - // - // error: invalid jump while there are to composed functions - // - // This could be caused by the `caller()` on stack is not consumed correctly - // - // // spend allowance - // let half_value = 21; - // let info = evm - // .calldata(&contract.encode(&[ - // b"spend_allowance(address,uint256)".to_vec(), - // spender.to_bytes32().to_vec(), - // half_value.to_bytes32().to_vec(), - // ])?) - // .call(address)?; - // println!("{info:?}"); - // assert_eq!(info.ret, true.to_bytes32()); - // let allowance = evm.storage( - // address, - // Allowance::storage_key(Address(evm.caller), Address(spender)), - // )?; - // assert_eq!(half_value.to_bytes32(), allowance); + // get on-chain storage + let info = evm + .calldata(&contract.encode(&[ + b"allowance(address,address)".to_vec(), + evm.caller.to_bytes32().to_vec(), + spender.to_bytes32().to_vec(), + ])?) + .call(address)?; + assert_eq!(info.ret, allowance); + + // spend allowance + let half_value = 21; + let info = evm + .calldata(&contract.encode(&[ + b"spend_allowance(address,uint256)".to_vec(), + spender.to_bytes32().to_vec(), + half_value.to_bytes32().to_vec(), + ])?) + .call(address)?; + assert_eq!(info.ret, true.to_bytes32()); + let allowance = evm.storage( + address, + Allowance::storage_key(Address(evm.caller), Address(spender)), + )?; + assert_eq!(half_value.to_bytes32(), allowance); Ok(()) } diff --git a/zink/src/ffi/mod.rs b/zink/src/ffi/mod.rs index 2b46443c5..410fcbce4 100644 --- a/zink/src/ffi/mod.rs +++ b/zink/src/ffi/mod.rs @@ -20,7 +20,7 @@ extern "C" { /// Equal operation for addresses pub fn u256_sub(this: U256, other: U256) -> U256; - /// Equal operation for addresses + /// Less than operation for addresses pub fn u256_lt(this: U256, other: U256) -> bool; /// Equal operation for addresses diff --git a/zink/src/primitives/address.rs b/zink/src/primitives/address.rs index 0f3431ec3..acd527cbf 100644 --- a/zink/src/primitives/address.rs +++ b/zink/src/primitives/address.rs @@ -22,6 +22,7 @@ impl Address { } /// Returns empty address + #[inline(always)] pub fn caller() -> Self { unsafe { ffi::evm::caller() } } diff --git a/zink/src/primitives/u256.rs b/zink/src/primitives/u256.rs index 65f42bb3f..f6fed0283 100644 --- a/zink/src/primitives/u256.rs +++ b/zink/src/primitives/u256.rs @@ -16,28 +16,33 @@ impl U256 { U256([0; 32]) } - /// add another value + /// u256 add + #[inline(always)] pub fn add(self, other: Self) -> Self { unsafe { ffi::u256_add(self, other) } } - /// add another value + /// u256 less than + #[inline(always)] pub fn lt(self, other: Self) -> bool { - unsafe { ffi::u256_lt(self, other) } + unsafe { ffi::u256_lt(other, self) } } - /// add another value + /// u256 sub + #[inline(always)] pub fn sub(self, other: Self) -> Self { - unsafe { ffi::u256_sub(self, other) } + unsafe { ffi::u256_sub(other, self) } } /// max of u256 + #[inline(always)] pub fn max() -> Self { unsafe { ffi::u256_max() } } } impl Asm for U256 { + #[inline(always)] fn push(self) { unsafe { ffi::asm::push_u256(self) } } @@ -49,6 +54,7 @@ impl Asm for U256 { } impl StorageValue for U256 { + #[inline(always)] fn sload() -> Self { unsafe { ffi::asm::sload_u256() } } diff --git a/zink/src/storage/dkmapping.rs b/zink/src/storage/dkmapping.rs index 5762536b6..d2c26c2c5 100644 --- a/zink/src/storage/dkmapping.rs +++ b/zink/src/storage/dkmapping.rs @@ -14,6 +14,7 @@ pub trait DoubleKeyMapping { fn storage_key(key1: Self::Key1, key2: Self::Key2) -> [u8; 32]; /// Get value from storage key. + #[inline(always)] fn get(key1: Self::Key1, key2: Self::Key2) -> Self::Value { load_double_key(key1, key2, Self::STORAGE_SLOT); Self::Value::sload() diff --git a/zint/src/contract.rs b/zint/src/contract.rs index e7eb6498e..328a36df6 100644 --- a/zint/src/contract.rs +++ b/zint/src/contract.rs @@ -60,7 +60,7 @@ impl Contract { let compiler = Compiler::new(config); self.artifact = compiler.compile(&self.wasm)?; - tracing::debug!("abi: {:#}", self.json_abi()?); + // tracing::debug!("abi: {:#}", self.json_abi()?); tracing::debug!("bytecode: {}", hex::encode(&self.artifact.runtime_bytecode)); Ok(self) }