//! Host functions exposed to WASM contracts. //! //! These functions provide contracts with access to blockchain state, //! cryptographic operations, and other capabilities. use synor_types::{Address, Hash256}; use crate::context::ExecutionContext; use crate::storage::{ContractStorage, StorageKey, StorageValue}; use crate::{ContractId, VmError}; /// Host functions available to contracts. pub struct HostFunctions; impl HostFunctions { // ==================== Storage Operations ==================== /// Reads a value from contract storage. pub fn storage_read( ctx: &mut ExecutionContext, key: &[u8; 32], ) -> Result>, VmError> { let storage_key = StorageKey::new(*key); // Charge gas ctx.gas .charge_storage_read(32) .map_err(|_| VmError::OutOfGas { used: ctx.gas.consumed(), limit: ctx.gas.limit(), })?; Ok(ctx .storage .get(&ctx.call.contract, &storage_key) .map(|v| v.0)) } /// Writes a value to contract storage. pub fn storage_write( ctx: &mut ExecutionContext, key: &[u8; 32], value: &[u8], ) -> Result<(), VmError> { if ctx.is_static() { return Err(VmError::ExecutionError( "Cannot write storage in static call".into(), )); } let storage_key = StorageKey::new(*key); // Charge gas based on value size ctx.gas .charge_storage_write(value.len()) .map_err(|_| VmError::OutOfGas { used: ctx.gas.consumed(), limit: ctx.gas.limit(), })?; ctx.storage.set( &ctx.call.contract, storage_key, StorageValue::new(value.to_vec()), ); Ok(()) } /// Deletes a value from contract storage. pub fn storage_delete( ctx: &mut ExecutionContext, key: &[u8; 32], ) -> Result { if ctx.is_static() { return Err(VmError::ExecutionError( "Cannot delete storage in static call".into(), )); } let storage_key = StorageKey::new(*key); // Add refund for deletion ctx.gas.add_refund(ctx.gas.config().storage_delete_refund); let existed = ctx .storage .delete(&ctx.call.contract, &storage_key) .is_some(); Ok(existed) } // ==================== Context Information ==================== /// Returns the caller address. pub fn get_caller(ctx: &ExecutionContext) -> Address { ctx.caller() } /// Returns the contract's own address. pub fn get_address(ctx: &ExecutionContext) -> Address { ctx.address() } /// Returns the transaction origin. pub fn get_origin(ctx: &ExecutionContext) -> Address { ctx.origin() } /// Returns the value sent with the call. pub fn get_value(ctx: &ExecutionContext) -> u64 { ctx.value() } /// Returns the call data. pub fn get_calldata(ctx: &ExecutionContext) -> &[u8] { ctx.calldata() } /// Returns the block timestamp. pub fn get_timestamp(ctx: &ExecutionContext) -> u64 { ctx.timestamp() } /// Returns the block height. pub fn get_block_height(ctx: &ExecutionContext) -> u64 { ctx.block_height() } /// Returns the block hash. pub fn get_block_hash(ctx: &ExecutionContext) -> Hash256 { ctx.block_hash() } /// Returns remaining gas. pub fn get_gas_remaining(ctx: &ExecutionContext) -> u64 { ctx.gas_remaining() } /// Returns the chain ID. pub fn get_chain_id(ctx: &ExecutionContext) -> u64 { ctx.chain_id } // ==================== Cryptographic Operations ==================== /// Computes SHA3-256 hash. pub fn sha3( ctx: &mut ExecutionContext, data: &[u8], ) -> Result<[u8; 32], VmError> { ctx.gas .charge_hash(data.len()) .map_err(|_| VmError::OutOfGas { used: ctx.gas.consumed(), limit: ctx.gas.limit(), })?; use sha3::{Digest, Sha3_256}; let result = Sha3_256::digest(data); Ok(result.into()) } /// Computes Blake3 hash. pub fn blake3( ctx: &mut ExecutionContext, data: &[u8], ) -> Result<[u8; 32], VmError> { ctx.gas .charge_hash(data.len()) .map_err(|_| VmError::OutOfGas { used: ctx.gas.consumed(), limit: ctx.gas.limit(), })?; Ok(blake3::hash(data).into()) } /// Verifies an Ed25519 signature. pub fn verify_signature( ctx: &mut ExecutionContext, _message: &[u8], _signature: &[u8; 64], _public_key: &[u8; 32], ) -> Result { ctx.gas.charge_signature().map_err(|_| VmError::OutOfGas { used: ctx.gas.consumed(), limit: ctx.gas.limit(), })?; // Use synor_crypto for verification // Simplified for now Ok(true) // Placeholder } // ==================== Contract Calls ==================== /// Calls another contract. pub fn call_contract( ctx: &mut ExecutionContext, _target: ContractId, value: u64, input: &[u8], _gas_limit: u64, ) -> Result, VmError> { if ctx.is_static() && value > 0 { return Err(VmError::ExecutionError( "Cannot send value in static call".into(), )); } // Charge gas for call ctx.gas .charge_call(input.len()) .map_err(|_| VmError::OutOfGas { used: ctx.gas.consumed(), limit: ctx.gas.limit(), })?; // This would trigger actual contract execution // For now, return empty success Ok(Vec::new()) } /// Makes a static (read-only) call to another contract. pub fn static_call( ctx: &mut ExecutionContext, _target: ContractId, input: &[u8], _gas_limit: u64, ) -> Result, VmError> { ctx.gas .charge_call(input.len()) .map_err(|_| VmError::OutOfGas { used: ctx.gas.consumed(), limit: ctx.gas.limit(), })?; Ok(Vec::new()) } /// Makes a delegate call (code runs in caller's context). pub fn delegate_call( ctx: &mut ExecutionContext, _target: ContractId, input: &[u8], _gas_limit: u64, ) -> Result, VmError> { if ctx.is_static() { return Err(VmError::ExecutionError( "Cannot delegate call in static call".into(), )); } ctx.gas .charge_call(input.len()) .map_err(|_| VmError::OutOfGas { used: ctx.gas.consumed(), limit: ctx.gas.limit(), })?; Ok(Vec::new()) } // ==================== Events ==================== /// Emits a log event. pub fn emit_log( ctx: &mut ExecutionContext, topics: Vec<[u8; 32]>, data: Vec, ) -> Result<(), VmError> { let topics: Vec = topics.into_iter().map(Hash256::from_bytes).collect(); ctx.emit_log(topics, data) } // ==================== Control Flow ==================== /// Reverts execution with a message. pub fn revert(message: &str) -> VmError { VmError::Revert(message.to_string()) } /// Returns successfully with data. pub fn return_data(ctx: &mut ExecutionContext, data: Vec) { ctx.set_return_data(data); } // ==================== Balance Operations ==================== /// Gets the balance of an address. pub fn get_balance( _ctx: &ExecutionContext, _address: &Address, ) -> Result { // Would query UTXO set Ok(0) } /// Transfers value to an address. pub fn transfer( ctx: &mut ExecutionContext, _to: &Address, _amount: u64, ) -> Result<(), VmError> { if ctx.is_static() { return Err(VmError::ExecutionError( "Cannot transfer in static call".into(), )); } // Would create transfer output Ok(()) } } /// WASM import definitions for host functions. /// These map to the actual wasmtime host function implementations. pub mod wasm_imports { /// Storage read signature: (key_ptr: i32, value_ptr: i32) -> i32 (length or -1) pub const STORAGE_READ: &str = "synor_storage_read"; /// Storage write signature: (key_ptr: i32, value_ptr: i32, value_len: i32) -> i32 pub const STORAGE_WRITE: &str = "synor_storage_write"; /// Storage delete signature: (key_ptr: i32) -> i32 (existed: 0/1) pub const STORAGE_DELETE: &str = "synor_storage_delete"; /// Get caller: (out_ptr: i32) -> () pub const GET_CALLER: &str = "synor_get_caller"; /// Get address: (out_ptr: i32) -> () pub const GET_ADDRESS: &str = "synor_get_address"; /// Get value: () -> i64 pub const GET_VALUE: &str = "synor_get_value"; /// Get calldata size: () -> i32 pub const GET_CALLDATA_SIZE: &str = "synor_get_calldata_size"; /// Get calldata: (out_ptr: i32, offset: i32, length: i32) -> () pub const GET_CALLDATA: &str = "synor_get_calldata"; /// Get timestamp: () -> i64 pub const GET_TIMESTAMP: &str = "synor_get_timestamp"; /// Get block height: () -> i64 pub const GET_BLOCK_HEIGHT: &str = "synor_get_block_height"; /// SHA3 hash: (data_ptr: i32, data_len: i32, out_ptr: i32) -> () pub const SHA3: &str = "synor_sha3"; /// Blake3 hash: (data_ptr: i32, data_len: i32, out_ptr: i32) -> () pub const BLAKE3: &str = "synor_blake3"; /// Emit log: (topics_ptr: i32, topics_count: i32, data_ptr: i32, data_len: i32) -> i32 pub const EMIT_LOG: &str = "synor_emit_log"; /// Call contract: (addr_ptr: i32, value: i64, data_ptr: i32, data_len: i32, gas: i64) -> i32 pub const CALL: &str = "synor_call"; /// Revert: (msg_ptr: i32, msg_len: i32) -> () pub const REVERT: &str = "synor_revert"; /// Return: (data_ptr: i32, data_len: i32) -> () pub const RETURN: &str = "synor_return"; } #[cfg(test)] mod tests { use super::*; use crate::context::CallContext; use crate::storage::MemoryStorage; use synor_types::Network; fn test_context() -> ExecutionContext { let contract = ContractId::from_bytes([0x42; 32]); let caller = Address::from_ed25519_pubkey(Network::Mainnet, &[0x11; 32]); let call = CallContext::new(contract, caller, 1000, vec![1, 2, 3]); ExecutionContext::::testing(call) } #[test] fn test_storage_operations() { let mut ctx = test_context(); let key = [0x01; 32]; let value = vec![1, 2, 3, 4]; // Write HostFunctions::storage_write(&mut ctx, &key, &value).unwrap(); ctx.storage.commit(); // Read let read = HostFunctions::storage_read(&mut ctx, &key).unwrap(); assert_eq!(read, Some(value.clone())); // Delete let existed = HostFunctions::storage_delete(&mut ctx, &key).unwrap(); assert!(existed); ctx.storage.commit(); let read = HostFunctions::storage_read(&mut ctx, &key).unwrap(); assert!(read.is_none()); } #[test] fn test_context_getters() { let ctx = test_context(); assert_eq!(HostFunctions::get_value(&ctx), 1000); assert_eq!(HostFunctions::get_calldata(&ctx), &[1, 2, 3]); assert!(HostFunctions::get_gas_remaining(&ctx) > 0); } #[test] fn test_hash_functions() { let mut ctx = test_context(); let data = b"hello world"; let sha3_result = HostFunctions::sha3(&mut ctx, data).unwrap(); let blake3_result = HostFunctions::blake3(&mut ctx, data).unwrap(); assert_ne!(sha3_result, blake3_result); assert_ne!(sha3_result, [0u8; 32]); } #[test] fn test_static_call_restrictions() { let contract = ContractId::from_bytes([0x42; 32]); let caller = Address::from_ed25519_pubkey(Network::Mainnet, &[0x11; 32]); let call = CallContext::static_call(contract, caller, vec![]); let mut ctx = ExecutionContext::::testing(call); // Storage write should fail let result = HostFunctions::storage_write(&mut ctx, &[0x01; 32], &[1, 2, 3]); assert!(result.is_err()); // Emit log should fail let result = ctx.emit_log(vec![], vec![]); assert!(result.is_err()); } }