From 0e766ca09dbadf8940e3c1fcc6e2106bc76ade5c Mon Sep 17 00:00:00 2001 From: scab24 Date: Sat, 16 Nov 2024 12:55:07 +0100 Subject: [PATCH 1/3] feat: implement syscall log and automation --- contract-derive/src/lib.rs | 100 +++++++++++ erc20/src/lib.rs | 31 +++- eth-riscv-runtime/src/lib.rs | 16 ++ eth-riscv-runtime/src/log.rs | 17 ++ eth-riscv-syscalls/src/lib.rs | 1 + r55/src/exec.rs | 52 +++++- r55/src/main.rs | 325 ++++++++++++++++++++++++++++++++++ rvemu.dtb | Bin 0 -> 1598 bytes 8 files changed, 536 insertions(+), 6 deletions(-) create mode 100644 eth-riscv-runtime/src/log.rs create mode 100644 rvemu.dtb diff --git a/contract-derive/src/lib.rs b/contract-derive/src/lib.rs index 1028295..c106289 100644 --- a/contract-derive/src/lib.rs +++ b/contract-derive/src/lib.rs @@ -87,11 +87,111 @@ pub fn contract(_attr: TokenStream, item: TokenStream) -> TokenStream { } }).collect(); + let emit_helper = quote! { + #[macro_export] + macro_rules! emit { + ($event_name:expr, $($arg:expr),*) => {{ + use alloy_sol_types::SolValue; + use alloy_core::primitives::{keccak256, B256, U256, I256}; + use alloc::vec::Vec; + use alloc::string::String; + use alloc::borrow::ToOwned; + + let mut signature = alloc::vec![]; + signature.extend_from_slice($event_name.as_bytes()); + signature.extend_from_slice(b"("); + + let mut first = true; + $( + if !first { + signature.extend_from_slice(b","); + } + first = false; + + signature.extend_from_slice(match stringify!($arg) { + // Address + s if s.contains("Address") || s.contains("address") => b"address", + + // Unsigned integers + s if s.contains("u8") => b"uint8", + s if s.contains("u16") => b"uint16", + s if s.contains("u32") => b"uint32", + s if s.contains("u64") => b"uint64", + s if s.contains("u128") => b"uint128", + s if s.contains("U256") || s.contains("uint256") => b"uint256", + + // Signed integers + s if s.contains("i8") => b"int8", + s if s.contains("i16") => b"int16", + s if s.contains("i32") => b"int32", + s if s.contains("i64") => b"int64", + s if s.contains("i128") => b"int128", + s if s.contains("I256") || s.contains("int256") => b"int256", + + // Boolean + s if s.contains("bool") => b"bool", + + // Bytes y FixedBytes + s if s.contains("B256") => b"bytes32", + s if s.contains("[u8; 32]") => b"bytes32", + s if s.contains("[u8; 20]") => b"bytes20", + s if s.contains("[u8; 16]") => b"bytes16", + s if s.contains("[u8; 8]") => b"bytes8", + s if s.contains("[u8; 4]") => b"bytes4", + s if s.contains("[u8; 1]") => b"bytes1", + + // Dynamic bytes & strings + s if s.contains("Vec") => b"bytes", + s if s.contains("String") || s.contains("str") => b"string", + + // Dynamic arrays + s if s.contains("Vec
") => b"address[]", + s if s.contains("Vec") => b"uint256[]", + s if s.contains("Vec") => b"bool[]", + s if s.contains("Vec") => b"bytes32[]", + + // Ztatic arrays + s if s.contains("[Address; ") => b"address[]", + s if s.contains("[U256; ") => b"uint256[]", + s if s.contains("[bool; ") => b"bool[]", + s if s.contains("[B256; ") => b"bytes32[]", + // Tuples + s if s.contains("(Address, U256)") => b"(address,uint256)", + s if s.contains("(U256, bool)") => b"(uint256,bool)", + s if s.contains("(Address, Address)") => b"(address,address)", + + _ => b"uint64", + }); + )* + + signature.extend_from_slice(b")"); + + let topic0 = B256::from(keccak256(&signature)); + let mut topics = alloc::vec![topic0]; + + $( + let encoded = $arg.abi_encode(); + if topics.len() < 3 { + topics.push(B256::from_slice(&encoded)); + } else { + eth_riscv_runtime::emit_log(&encoded, &topics); + } + )* + + if topics.len() > 1 { + let data = topics.pop().unwrap(); + eth_riscv_runtime::emit_log(data.as_ref(), &topics); + } + }}; + } + }; + // Generate the call method implementation let call_method = quote! { use alloy_sol_types::SolValue; use eth_riscv_runtime::*; + #emit_helper impl Contract for #struct_name { fn call(&self) { self.call_with_data(&msg_data()); diff --git a/erc20/src/lib.rs b/erc20/src/lib.rs index 0848cca..01ed683 100644 --- a/erc20/src/lib.rs +++ b/erc20/src/lib.rs @@ -7,6 +7,7 @@ use contract_derive::{contract, payable}; use eth_riscv_runtime::types::Mapping; use alloy_core::primitives::{Address, address, U256}; +extern crate alloc; #[derive(Default)] pub struct ERC20 { @@ -19,7 +20,7 @@ impl ERC20 { self.balance.read(owner) } - pub fn transfer(&self, from: Address, to: Address, value: u64) { + pub fn transfer(&self, from: Address, to: Address, value: u64) -> bool { let from_balance = self.balance.read(from); let to_balance = self.balance.read(to); @@ -29,10 +30,13 @@ impl ERC20 { self.balance.write(from, from_balance - value); self.balance.write(to, to_balance + value); + + emit!("Transfer", from, to, value); + true } #[payable] - pub fn mint(&self, to: Address, value: u64) { + pub fn mint(&self, to: Address, value: u64) -> bool { let owner = msg_sender(); if owner != address!("0000000000000000000000000000000000000007") { revert(); @@ -40,5 +44,28 @@ impl ERC20 { let to_balance = self.balance.read(to); self.balance.write(to, to_balance + value); + emit!("Mint", to, value); + true + } + + pub fn burn(&self, from: Address, value: u64) -> bool { + let from_balance = self.balance.read(from); + if from_balance < value { + revert(); + } + + self.balance.write(from, from_balance - value); + emit!("Burn", value); + true + } + + pub fn set_paused(&self, paused: bool) -> bool { + emit!("PauseChanged", paused); + true + } + + pub fn update_metadata(&self, data: [u8; 32]) -> bool { + emit!("MetadataUpdated", data); + true } } diff --git a/eth-riscv-runtime/src/lib.rs b/eth-riscv-runtime/src/lib.rs index f97dcbe..6e9ce88 100644 --- a/eth-riscv-runtime/src/lib.rs +++ b/eth-riscv-runtime/src/lib.rs @@ -12,6 +12,9 @@ mod alloc; pub mod types; pub mod block; +mod log; +pub use log::emit_log; + const CALLDATA_ADDRESS: usize = 0x8000_0000; pub trait Contract { @@ -141,6 +144,19 @@ pub fn msg_data() -> &'static [u8] { unsafe { slice_from_raw_parts(CALLDATA_ADDRESS + 8, length) } } +pub fn log(data_ptr: u64, data_size: u64, topics_ptr: u64, topics_size: u64) { + unsafe { + asm!( + "ecall", + in("a0") data_ptr, + in("a1") data_size, + in("a2") topics_ptr, + in("a3") topics_size, + in("t0") u8::from(Syscall::Log) + ); + } +} + #[allow(non_snake_case)] #[no_mangle] fn DefaultHandler() { diff --git a/eth-riscv-runtime/src/log.rs b/eth-riscv-runtime/src/log.rs new file mode 100644 index 0000000..1e8aed1 --- /dev/null +++ b/eth-riscv-runtime/src/log.rs @@ -0,0 +1,17 @@ +use alloy_core::primitives::B256; + +pub fn emit_log(data: &[u8], topics: &[B256]) { + let mut all_topics = [0u8; 96]; + for (i, topic) in topics.iter().enumerate() { + if i >= 3 { break; } + let start = i * 32; + all_topics[start..start + 32].copy_from_slice(topic.as_ref()); + } + + crate::log( + data.as_ptr() as u64, + data.len() as u64, + all_topics.as_ptr() as u64, + topics.len() as u64 + ); +} \ No newline at end of file diff --git a/eth-riscv-syscalls/src/lib.rs b/eth-riscv-syscalls/src/lib.rs index 10dd5e1..a1dcb63 100644 --- a/eth-riscv-syscalls/src/lib.rs +++ b/eth-riscv-syscalls/src/lib.rs @@ -71,4 +71,5 @@ syscalls!( (0xf1, Call, "call"), (0xf3, Return, "return"), (0xfd, Revert, "revert"), + (0xA0, Log, "log"), ); diff --git a/r55/src/exec.rs b/r55/src/exec.rs index 203bdb9..1a75017 100644 --- a/r55/src/exec.rs +++ b/r55/src/exec.rs @@ -7,7 +7,7 @@ use revm::{ CallInputs, CallScheme, CallValue, Host, InstructionResult, Interpreter, InterpreterAction, InterpreterResult, SharedMemory, }, - primitives::{address, Address, Bytes, ExecutionResult, Output, TransactTo, U256}, + primitives::{address, Address, Bytes, ExecutionResult, Output, TransactTo, U256, B256, Log}, Database, Evm, Frame, FrameOrResult, InMemoryDB, }; use rvemu::{emulator::Emulator, exception::Exception}; @@ -39,8 +39,12 @@ pub fn deploy_contract(db: &mut InMemoryDB, bytecode: Bytes) -> Address { result => panic!("Unexpected result: {:?}", result), } } +pub struct TxResult { + pub output: Vec, + pub logs: Vec +} -pub fn run_tx(db: &mut InMemoryDB, addr: &Address, calldata: Vec) { +pub fn run_tx(db: &mut InMemoryDB, addr: &Address, calldata: Vec) -> TxResult { let mut evm = Evm::builder() .with_db(db) .modify_tx_env(|tx| { @@ -57,10 +61,17 @@ pub fn run_tx(db: &mut InMemoryDB, addr: &Address, calldata: Vec) { match result { ExecutionResult::Success { output: Output::Call(value), + logs, .. - } => println!("Tx result: {:?}", value), + } => { + println!("Tx result: {:?}", value); + TxResult { + output:value.into(), + logs + } + }, result => panic!("Unexpected result: {:?}", result), - }; + } } #[derive(Debug)] @@ -324,6 +335,39 @@ fn execute_riscv( emu.cpu.xregs.write(12, limbs[2]); emu.cpu.xregs.write(13, limbs[3]); } + Syscall::Log => { + let data_ptr: u64 = emu.cpu.xregs.read(10); + let data_size: u64 = emu.cpu.xregs.read(11); + let topics_ptr: u64 = emu.cpu.xregs.read(12); + let topics_size: u64 = emu.cpu.xregs.read(13); + + let data = if data_size > 0 { + emu.cpu.bus + .get_dram_slice(data_ptr..(data_ptr + data_size)) + .unwrap_or_default() + .to_vec() + } else { + vec![] + }; + + let mut topics = Vec::new(); + if topics_size > 0 { + for i in 0..topics_size { + let topic_ptr = topics_ptr + (i * 32); + if let Ok(topic_data) = emu.cpu.bus + .get_dram_slice(topic_ptr..(topic_ptr + 32)) + { + topics.push(B256::from_slice(topic_data)); + } + } + } + + host.log(Log::new_unchecked( + interpreter.contract.target_address, + topics, + data.into(), + )); + } } } _ => { diff --git a/r55/src/main.rs b/r55/src/main.rs index febfb3d..7758dc6 100644 --- a/r55/src/main.rs +++ b/r55/src/main.rs @@ -10,6 +10,7 @@ use revm::{ primitives::{address, keccak256, ruint::Uint, AccountInfo, Address, Bytecode, Bytes}, InMemoryDB, }; +use alloy_core::hex; fn compile_runtime(path: &str) -> Result, ()> { println!("Compiling runtime: {}", path); @@ -183,7 +184,331 @@ fn test_deploy() { test_runtime(&addr, &mut db); } + +////////////////////////// +/// TESTS // +////////////////////////// + +fn parse_hex_result(result: &[u8]) -> u64 { + let mut bytes = [0u8; 8]; + bytes.copy_from_slice(&result[24..32]); + u64::from_be_bytes(bytes) +} + +fn parse_bool_result(hex_result: &[u8]) -> bool { + if hex_result.len() == 32 { + hex_result[31] == 1 + } else { + false + } +} + +fn test_erc20_mint_and_transfer() { + let rv_bytecode = compile_runtime("erc20").unwrap(); + println!("Bytecode length: {}", rv_bytecode.len()); + + const CONTRACT_ADDR: Address = address!("0d4a11d5EEaaC28EC3F61d100daF4d40471f1852"); + let mut db = InMemoryDB::default(); + + let mut bytecode = vec![0xff]; + bytecode.extend_from_slice(&rv_bytecode); + let bytecode = Bytes::from(bytecode); + add_contract_to_db(&mut db, CONTRACT_ADDR, bytecode); + + let selector_balance = &keccak256("balance_of")[0..4]; + let selector_transfer = &keccak256("transfer")[0..4]; + let selector_mint = &keccak256("mint")[0..4]; + + let address1: Address = address!("0000000000000000000000000000000000000001"); + let address2: Address = address!("0000000000000000000000000000000000000002"); + + // Mint 100 tokens + let mint_value: u64 = 100; + let mut calldata_mint = (address1, mint_value).abi_encode(); + let mut complete_calldata_mint = selector_mint.to_vec(); + complete_calldata_mint.append(&mut calldata_mint); + + println!("\n=== Minting tokens ==="); + println!("Minting {} tokens to address1...", mint_value); + let mint_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_mint); + println!("Mint tx result: {}", parse_bool_result(&mint_result.output)); + + // Print mint logs + println!("\nMint Event Logs:"); + for log in mint_result.logs { + println!(" Event Signature: 0x{}", hex::encode(&log.topics()[0])); + if log.topics().len() > 1 { + println!(" Address: 0x{}", hex::encode(&log.topics()[1])); + } + println!(" Data: 0x{}", hex::encode(&log.data.data)); + } + + // Check initial balance + let mut calldata_balance = address1.abi_encode(); + let mut complete_calldata_balance = selector_balance.to_vec(); + complete_calldata_balance.append(&mut calldata_balance); + + println!("\n=== Checking initial balance ==="); + let balance_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_balance.clone()); + let initial_balance = parse_hex_result(&balance_result.output); + println!("Address1 balance: {} (hex: 0x{})", initial_balance, hex::encode(&balance_result.output)); + assert_eq!(initial_balance, mint_value, "Initial balance should be {}", mint_value); + + // Transfer 30 tokens + let transfer_value: u64 = 30; + let mut calldata_transfer = (address1, address2, transfer_value).abi_encode(); + let mut complete_calldata_transfer = selector_transfer.to_vec(); + complete_calldata_transfer.append(&mut calldata_transfer); + + println!("\n=== Transferring tokens ==="); + println!("Transferring {} tokens from address1 to address2...", transfer_value); + let transfer_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_transfer); + println!("Transfer tx result: {}", parse_bool_result(&transfer_result.output)); + + // Print transfer logs + println!("\nTransfer Event Logs:"); + for log in transfer_result.logs { + println!(" Event Signature: 0x{}", hex::encode(&log.topics()[0])); + for (i, topic) in log.topics().iter().enumerate().skip(1) { + println!(" Topic {}: 0x{}", i, hex::encode(topic)); + } + println!(" Data: 0x{}", hex::encode(&log.data.data)); + } + + // Check final balances + println!("\n=== Checking final balances ==="); + let balance_result1 = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_balance.clone()); + let final_balance1 = parse_hex_result(&balance_result1.output); + println!("Address1 final balance: {} (hex: 0x{})", final_balance1, hex::encode(&balance_result1.output)); + assert_eq!(final_balance1, mint_value - transfer_value, + "Address1 should have {} tokens", mint_value - transfer_value); + + let mut calldata_balance2 = address2.abi_encode(); + let mut complete_calldata_balance2 = selector_balance.to_vec(); + complete_calldata_balance2.append(&mut calldata_balance2); + + let balance_result2 = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_balance2); + let final_balance2 = parse_hex_result(&balance_result2.output); + println!("Address2 final balance: {} (hex: 0x{})", final_balance2, hex::encode(&balance_result2.output)); + assert_eq!(final_balance2, transfer_value, + "Address2 should have {} tokens", transfer_value); + + println!("\n=== Test Summary ==="); + println!("✓ Initial mint: {} tokens to address1", mint_value); + println!("✓ Initial balance of address1: {} tokens", initial_balance); + println!("✓ Transfer: {} tokens from address1 to address2", transfer_value); + println!("✓ Final balance of address1: {} tokens", final_balance1); + println!("✓ Final balance of address2: {} tokens", final_balance2); +} + +fn test_transfer_event_values() { + let rv_bytecode = compile_runtime("erc20").unwrap(); + const CONTRACT_ADDR: Address = address!("0d4a11d5EEaaC28EC3F61d100daF4d40471f1852"); + let mut db = InMemoryDB::default(); + + let mut bytecode = vec![0xff]; + bytecode.extend_from_slice(&rv_bytecode); + let bytecode = Bytes::from(bytecode); + add_contract_to_db(&mut db, CONTRACT_ADDR, bytecode); + + let from_addr = address!("0000000000000000000000000000000000000001"); + let to_addr = address!("0000000000000000000000000000000000000002"); + let mint_amount = 1000u64; + let transfer_amount = 500u64; + + // Mint + let selector_mint = &keccak256("mint")[0..4]; + let mut calldata_mint = (from_addr, mint_amount).abi_encode(); + let mut complete_calldata_mint = selector_mint.to_vec(); + complete_calldata_mint.append(&mut calldata_mint); + let mint_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_mint); + assert!(parse_bool_result(&mint_result.output), "Mint failed"); + + // Transfer + let selector_transfer = &keccak256("transfer")[0..4]; + let mut calldata_transfer = (from_addr, to_addr, transfer_amount).abi_encode(); + let mut complete_calldata_transfer = selector_transfer.to_vec(); + complete_calldata_transfer.append(&mut calldata_transfer); + let transfer_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_transfer); + + // Print events + let log = &transfer_result.logs[0]; + let log_mint = &mint_result.logs[0]; + + println!("\n=== Transfer Event - Expected vs Actual Values ==="); + println!("Contract Address:"); + println!(" Expected: {:?}", CONTRACT_ADDR); + println!(" Actual : {:?}", log.address); + println!(); + + println!("From Address:"); + println!(" Expected: {:?}", from_addr); + println!(" Actual : 0x{}", hex::encode(&log.topics()[1])); + println!(); + + println!("To Address:"); + println!(" Expected: {:?}", to_addr); + println!(" Actual : 0x{}", hex::encode(&log.topics()[2])); + println!(); + + println!("Transfer Amount:"); + println!(" Expected: {} tokens", transfer_amount); + println!(" Actual : {} tokens", parse_hex_result(&log.data.data)); + + println!("\n=== MINT Event - Expected vs Actual Values ==="); + println!("Contract Address:"); + println!(" Expected: {:?}", CONTRACT_ADDR); + println!(" Actual : {:?}", log_mint.address); + println!(); + + println!("TO Address:"); + println!(" Expected: {:?}", from_addr); + println!(" Actual : 0x{}", hex::encode(&log_mint.topics()[1])); + println!(); + + println!("Transfer Amount:"); + println!(" Expected: {} tokens", mint_amount); + println!(" Actual : {} tokens", parse_hex_result(&log_mint.data.data)); + + assert_eq!(log.address, CONTRACT_ADDR, "Contract address mismatch"); + assert_eq!(parse_hex_result(&log.data.data), transfer_amount, "Transfer amount mismatch"); +} + +fn test_burn_event() { + let rv_bytecode = compile_runtime("erc20").unwrap(); + const CONTRACT_ADDR: Address = address!("0d4a11d5EEaaC28EC3F61d100daF4d40471f1852"); + let mut db = InMemoryDB::default(); + + let mut bytecode = vec![0xff]; + bytecode.extend_from_slice(&rv_bytecode); + let bytecode = Bytes::from(bytecode); + add_contract_to_db(&mut db, CONTRACT_ADDR, bytecode); + + let address1 = address!("0000000000000000000000000000000000000001"); + let burn_amount = 500u64; + + // Mint + let selector_mint = &keccak256("mint")[0..4]; + let mut calldata_mint = (address1, 1000u64).abi_encode(); + let mut complete_calldata_mint = selector_mint.to_vec(); + complete_calldata_mint.append(&mut calldata_mint); + run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_mint); + + // Burn + let selector_burn = &keccak256("burn")[0..4]; + let mut calldata_burn = (address1, burn_amount).abi_encode(); + let mut complete_calldata_burn = selector_burn.to_vec(); + complete_calldata_burn.append(&mut calldata_burn); + + let burn_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_burn); + let log = &burn_result.logs[0]; + + println!("\nEmitted Events:"); + println!(" Burn(uint64)"); + println!(" value: {} (input: {})", parse_hex_result(&log.data.data), burn_amount); + + assert_eq!(parse_hex_result(&log.data.data), burn_amount, "Event value doesn't match input value"); +} + +fn test_pause_event() { + let rv_bytecode = compile_runtime("erc20").unwrap(); + const CONTRACT_ADDR: Address = address!("0d4a11d5EEaaC28EC3F61d100daF4d40471f1852"); + let mut db = InMemoryDB::default(); + + let mut bytecode = vec![0xff]; + bytecode.extend_from_slice(&rv_bytecode); + let bytecode = Bytes::from(bytecode); + add_contract_to_db(&mut db, CONTRACT_ADDR, bytecode); + + let pause_state = true; + let selector_pause = &keccak256("set_paused")[0..4]; + let mut calldata_pause = pause_state.abi_encode(); + let mut complete_calldata_pause = selector_pause.to_vec(); + complete_calldata_pause.append(&mut calldata_pause); + + let pause_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_pause); + let log = &pause_result.logs[0]; + + println!("\nEmitted Events:"); + println!(" PauseChanged(bool)"); + println!(" paused: {} (input: {})", parse_bool_result(&log.data.data), pause_state); + + assert_eq!(parse_bool_result(&log.data.data), pause_state, "Wrong pause state"); +} + +fn test_metadata_event() { + let rv_bytecode = compile_runtime("erc20").unwrap(); + const CONTRACT_ADDR: Address = address!("0d4a11d5EEaaC28EC3F61d100daF4d40471f1852"); + let mut db = InMemoryDB::default(); + + let mut bytecode = vec![0xff]; + bytecode.extend_from_slice(&rv_bytecode); + let bytecode = Bytes::from(bytecode); + add_contract_to_db(&mut db, CONTRACT_ADDR, bytecode); + + let metadata: [u8; 32] = [1u8; 32]; + let selector_metadata = &keccak256("update_metadata")[0..4]; + let mut calldata_metadata = metadata.abi_encode(); + let mut complete_calldata_metadata = selector_metadata.to_vec(); + complete_calldata_metadata.append(&mut calldata_metadata); + + let metadata_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_metadata); + let log = &metadata_result.logs[0]; + + println!("\nEmitted Events:"); + println!(" MetadataUpdated(bytes32)"); + println!(" data: 0x{} (input: 0x{})", hex::encode(&log.data.data), hex::encode(&metadata)); + + assert_eq!(log.data.data.as_ref(), &metadata[..], "Wrong metadata"); +} + +fn test_transfer_event() { + let rv_bytecode = compile_runtime("erc20").unwrap(); + const CONTRACT_ADDR: Address = address!("0d4a11d5EEaaC28EC3F61d100daF4d40471f1852"); + let mut db = InMemoryDB::default(); + + let mut bytecode = vec![0xff]; + bytecode.extend_from_slice(&rv_bytecode); + let bytecode = Bytes::from(bytecode); + add_contract_to_db(&mut db, CONTRACT_ADDR, bytecode); + + let from = address!("0000000000000000000000000000000000000001"); + let to = address!("0000000000000000000000000000000000000002"); + let amount = 1000u64; + + // Mint + let selector_mint = &keccak256("mint")[0..4]; + let mut calldata_mint = (from, amount * 2).abi_encode(); + let mut complete_calldata_mint = selector_mint.to_vec(); + complete_calldata_mint.append(&mut calldata_mint); + run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_mint); + + // Transfer + let selector_transfer = &keccak256("transfer")[0..4]; + let mut calldata_transfer = (from, to, amount).abi_encode(); + let mut complete_calldata_transfer = selector_transfer.to_vec(); + complete_calldata_transfer.append(&mut calldata_transfer); + + let transfer_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_transfer); + let log = &transfer_result.logs[0]; + + println!("\nEmitted Events:"); + println!(" Transfer(address,address,uint64)"); + println!(" from : {} (input: {})", hex::encode(&log.topics()[1]), hex::encode(from)); + println!(" to : {} (input: {})", hex::encode(&log.topics()[2]), hex::encode(to)); + println!(" value: {} (input: {})", parse_hex_result(&log.data.data), amount); + + assert_eq!(Address::from_slice(&log.topics()[1][12..]), from, "Wrong from address"); + assert_eq!(Address::from_slice(&log.topics()[2][12..]), to, "Wrong to address"); + assert_eq!(parse_hex_result(&log.data.data), amount, "Wrong transfer amount"); +} fn main() { test_runtime_from_binary(); test_deploy(); + test_erc20_mint_and_transfer(); + test_transfer_event_values(); + test_burn_event(); + test_pause_event(); + test_metadata_event(); + test_transfer_event(); } diff --git a/rvemu.dtb b/rvemu.dtb new file mode 100644 index 0000000000000000000000000000000000000000..47c9f54671a68d8e9b2f850ba5abb28a3ae680bc GIT binary patch literal 1598 zcmah}J#Q2-5cL8H1c(TNz5=;-OFQMBiEl2vxs8{2#0s8Uc+ra}~S z)U^BsT7Cc>HT(p`d-nQn6Ob5b&NK7o%j4Pmw*LF45UbCG5c@)`JjMAKd>_041|`>E zzY*k1ze%TE#~E?>im2a%9QAd`15;QzOJ{{~g@#U|?*s6oJ~lj4RZA4b<%zbc_A-4R zf`>+Hcd(tS+4d~YHjUnty0*Gh2hPo3juyVGKi+OL<0dH2FH86 z=;Wxs68jUFJkA+aRJvh7@?)Xz8hB9KoxSmRltCu>+F=5RRaI!;8M_Pcq9%J_qFA%v zL$7A6>QDOK1rI|0A@f5GHD%LUDxjE?a=!-WMT+>D`0)N5dV<)r${J#cEL-c88BqZx zQXADNG43txVBR${b4;%Iz3cp9+y#7KTGaP*?erC#AN6TArvr#FtKhIxv;-UX?30=8 z?G{ybluO&Kk@vp??6cpqp`Gnw+d)ZD<2I&kT#6-fMwg{B9$EJBg??bxBz*-_=Db4v zAt!`P5|(8g?-%bM&!ke7HuK4TZy}lMCio&?kV1|PoPGx9w194fgSir`eKE%V@7v?O z2ezo1Ql-;w3m8ZG4*us1CbIX%T)q=yW8EK+$DO|DmrF?M8lx|xlXJ(&&k=95C)td|PJrjlf3N z$ZK0!o*QLtDpgrpv1N-ls*|Ozn#>i`Qi)3EsuWZFXJzKt3hQ#+x)kV%SS+7iia?C2 zosgxLr|FSVXRWH`To`pMTv4fMW>wG3Z>BlP>Kv8uk61``=;n<=nats>!zc@ziK=Q- gk8CZfcD?rW Date: Sun, 17 Nov 2024 13:36:32 +0100 Subject: [PATCH 2/3] Automated event indexing with idx macro --- .DS_Store | Bin 0 -> 8196 bytes contract-derive/src/lib.rs | 211 +++++++++++++++++++++++-------------- erc20/src/lib.rs | 6 +- 3 files changed, 137 insertions(+), 80 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..d66b54ce4d77d1c3aff93b1d919814a74ce2cb1b GIT binary patch literal 8196 zcmeHLziU%b6#iZsC8R^!ltKjyZ&B(HwL(WF4RO)Ut_n$$m==;IB&ms8+!Xu=+&VZ2 zg1i5KgSbdh2M2c-M_0f5gXG-&Fz<;-zM}1=n$oL2(GqrKn=&9C_1eyUx!Gt<%=8030zLvh0zLvh0zLwZ5&@pss-giW-j4b?W!T*|F3$ znuWqay?MZ~)VG!I7mM9@d#=9nTYc+Ld>dE}3B@`}=%9ft+Bm^6y7ZxeD*so}r>=n( z>Zm$iA$S%YDSghQ_paod+UJON8_f};_-Kc(*~~I1&*O@Y%XaN8*+#-iu4k8rTq9yf zOygo*o<+Kg74mHAe72dzGV_{6g*k0AzmLZAdFkGKgyeST)`uSnv-alLk@<^`a{1&G zpL5#xGH&3y%uf1R=C!Mpe!hC~^Sxbf0xx}NsjCNVNN4rM=axFU7jJ`#S+)) zE_U5qJ>M8-g!O~CEBd1_)4o=4?lez*Gt=9`k(7Pv&T61 zkRHsMBmLO=YTrhcH;dPnj7yuU{=Yr@`~TuO?mhAm@DccH5eSln{X(9%v$gfaja983 zvKQG^Q}YH!Y6vg79H-RfIHfJe_1utC6((opz(|g;{qqk2qSBCie!Np$o&@ob@_Yba f-Pj_X#q#_D{P{orcHqYKRK?XfyJh_R`49X8^u98% literal 0 HcmV?d00001 diff --git a/contract-derive/src/lib.rs b/contract-derive/src/lib.rs index c106289..68c42e6 100644 --- a/contract-derive/src/lib.rs +++ b/contract-derive/src/lib.rs @@ -88,101 +88,158 @@ pub fn contract(_attr: TokenStream, item: TokenStream) -> TokenStream { }).collect(); let emit_helper = quote! { + #[macro_export] + macro_rules! get_type_signature { + ($arg:expr) => { + match stringify!($arg) { + // Address + s if s.contains("Address") || s.contains("address") => b"address", + + // Unsigned integers + s if s.contains("u8") => b"uint8", + s if s.contains("u16") => b"uint16", + s if s.contains("u32") => b"uint32", + s if s.contains("u64") => b"uint64", + s if s.contains("u128") => b"uint128", + s if s.contains("U256") || s.contains("uint256") => b"uint256", + + // Signed integers + s if s.contains("i8") => b"int8", + s if s.contains("i16") => b"int16", + s if s.contains("i32") => b"int32", + s if s.contains("i64") => b"int64", + s if s.contains("i128") => b"int128", + s if s.contains("I256") || s.contains("int256") => b"int256", + + // Boolean + s if s.contains("bool") => b"bool", + + // Bytes y FixedBytes + s if s.contains("B256") => b"bytes32", + s if s.contains("[u8; 32]") => b"bytes32", + s if s.contains("[u8; 20]") => b"bytes20", + s if s.contains("[u8; 16]") => b"bytes16", + s if s.contains("[u8; 8]") => b"bytes8", + s if s.contains("[u8; 4]") => b"bytes4", + s if s.contains("[u8; 1]") => b"bytes1", + + // Dynamic bytes & strings + s if s.contains("Vec") => b"bytes", + s if s.contains("String") || s.contains("str") => b"string", + + // Dynamic arrays + s if s.contains("Vec
") => b"address[]", + s if s.contains("Vec") => b"uint256[]", + s if s.contains("Vec") => b"bool[]", + s if s.contains("Vec") => b"bytes32[]", + + // Static arrays + s if s.contains("[Address; ") => b"address[]", + s if s.contains("[U256; ") => b"uint256[]", + s if s.contains("[bool; ") => b"bool[]", + s if s.contains("[B256; ") => b"bytes32[]", + + // Tuples + s if s.contains("(Address, U256)") => b"(address,uint256)", + s if s.contains("(U256, bool)") => b"(uint256,bool)", + s if s.contains("(Address, Address)") => b"(address,address)", + + _ => b"uint64", + } + }; + } + #[macro_export] macro_rules! emit { - ($event_name:expr, $($arg:expr),*) => {{ + // Handle multiple arguments: emit!(event_name, idx arg1, arg2, idx arg3, ...) + ($event_name:expr, $($val:tt)+) => {{ use alloy_sol_types::SolValue; use alloy_core::primitives::{keccak256, B256, U256, I256}; use alloc::vec::Vec; - use alloc::string::String; - use alloc::borrow::ToOwned; let mut signature = alloc::vec![]; signature.extend_from_slice($event_name.as_bytes()); signature.extend_from_slice(b"("); + // Initialize topics[0] for signature hash let mut first = true; - $( - if !first { - signature.extend_from_slice(b","); - } - first = false; - - signature.extend_from_slice(match stringify!($arg) { - // Address - s if s.contains("Address") || s.contains("address") => b"address", - - // Unsigned integers - s if s.contains("u8") => b"uint8", - s if s.contains("u16") => b"uint16", - s if s.contains("u32") => b"uint32", - s if s.contains("u64") => b"uint64", - s if s.contains("u128") => b"uint128", - s if s.contains("U256") || s.contains("uint256") => b"uint256", - - // Signed integers - s if s.contains("i8") => b"int8", - s if s.contains("i16") => b"int16", - s if s.contains("i32") => b"int32", - s if s.contains("i64") => b"int64", - s if s.contains("i128") => b"int128", - s if s.contains("I256") || s.contains("int256") => b"int256", - - // Boolean - s if s.contains("bool") => b"bool", - - // Bytes y FixedBytes - s if s.contains("B256") => b"bytes32", - s if s.contains("[u8; 32]") => b"bytes32", - s if s.contains("[u8; 20]") => b"bytes20", - s if s.contains("[u8; 16]") => b"bytes16", - s if s.contains("[u8; 8]") => b"bytes8", - s if s.contains("[u8; 4]") => b"bytes4", - s if s.contains("[u8; 1]") => b"bytes1", - - // Dynamic bytes & strings - s if s.contains("Vec") => b"bytes", - s if s.contains("String") || s.contains("str") => b"string", - - // Dynamic arrays - s if s.contains("Vec
") => b"address[]", - s if s.contains("Vec") => b"uint256[]", - s if s.contains("Vec") => b"bool[]", - s if s.contains("Vec") => b"bytes32[]", - - // Ztatic arrays - s if s.contains("[Address; ") => b"address[]", - s if s.contains("[U256; ") => b"uint256[]", - s if s.contains("[bool; ") => b"bool[]", - s if s.contains("[B256; ") => b"bytes32[]", - // Tuples - s if s.contains("(Address, U256)") => b"(address,uint256)", - s if s.contains("(U256, bool)") => b"(uint256,bool)", - s if s.contains("(Address, Address)") => b"(address,address)", - - _ => b"uint64", - }); - )* + let mut topics = alloc::vec![B256::default()]; + let mut data = Vec::new(); - signature.extend_from_slice(b")"); + process_args!(signature, first, topics, data, $($val)+); - let topic0 = B256::from(keccak256(&signature)); - let mut topics = alloc::vec![topic0]; - - $( - let encoded = $arg.abi_encode(); - if topics.len() < 3 { - topics.push(B256::from_slice(&encoded)); - } else { - eth_riscv_runtime::emit_log(&encoded, &topics); - } - )* + signature.extend_from_slice(b")"); + topics[0] = B256::from(keccak256(&signature)); - if topics.len() > 1 { + if !data.is_empty() { + eth_riscv_runtime::emit_log(&data, &topics); + } else if topics.len() > 1 { let data = topics.pop().unwrap(); eth_riscv_runtime::emit_log(data.as_ref(), &topics); } }}; + + // Handle single argument: emit!(event_name, arg) + ($event_name:expr, $val:expr) => {{ + use alloy_sol_types::SolValue; + use alloy_core::primitives::{keccak256, B256, U256, I256}; + use alloc::vec::Vec; + + let mut signature = alloc::vec![]; + signature.extend_from_slice($event_name.as_bytes()); + signature.extend_from_slice(b"("); + signature.extend_from_slice(get_type_signature!($val)); + signature.extend_from_slice(b")"); + + let topic0 = B256::from(keccak256(&signature)); + let topics = alloc::vec![topic0]; + + let encoded = $val.abi_encode(); + eth_riscv_runtime::emit_log(&encoded, &topics); + }}; + } + + #[macro_export] + macro_rules! process_args { + // Process final non-indexed value + ($sig:expr, $first:expr, $topics:expr, $data:expr, $val:expr) => {{ + if !$first { $sig.extend_from_slice(b","); } + $sig.extend_from_slice(get_type_signature!($val)); + let encoded = $val.abi_encode(); + $data.extend_from_slice(&encoded); + }}; + + // Process final indexed value (idx) + ($sig:expr, $first:expr, $topics:expr, $data:expr, idx $val:expr) => {{ + if !$first { $sig.extend_from_slice(b","); } + $sig.extend_from_slice(get_type_signature!($val)); + let encoded = $val.abi_encode(); + if $topics.len() < 4 { // EVM limit: max 4 topics + $topics.push(B256::from_slice(&encoded)); + } + }}; + + // Process indexed value recursively + ($sig:expr, $first:expr, $topics:expr, $data:expr, idx $val:expr, $($rest:tt)+) => {{ + if !$first { $sig.extend_from_slice(b","); } + $first = false; + $sig.extend_from_slice(get_type_signature!($val)); + let encoded = $val.abi_encode(); + if $topics.len() < 4 { + $topics.push(B256::from_slice(&encoded)); + } + process_args!($sig, $first, $topics, $data, $($rest)+); + }}; + + // Process non-indexed value recursively + ($sig:expr, $first:expr, $topics:expr, $data:expr, $val:expr, $($rest:tt)+) => {{ + if !$first { $sig.extend_from_slice(b","); } + $first = false; + $sig.extend_from_slice(get_type_signature!($val)); + let encoded = $val.abi_encode(); + $data.extend_from_slice(&encoded); + process_args!($sig, $first, $topics, $data, $($rest)+); + }}; } }; diff --git a/erc20/src/lib.rs b/erc20/src/lib.rs index 01ed683..6481fb7 100644 --- a/erc20/src/lib.rs +++ b/erc20/src/lib.rs @@ -31,7 +31,7 @@ impl ERC20 { self.balance.write(from, from_balance - value); self.balance.write(to, to_balance + value); - emit!("Transfer", from, to, value); + emit!("Transfer", idx from, idx to, value); true } @@ -44,7 +44,7 @@ impl ERC20 { let to_balance = self.balance.read(to); self.balance.write(to, to_balance + value); - emit!("Mint", to, value); + emit!("Mint", idx to, value); true } @@ -55,7 +55,7 @@ impl ERC20 { } self.balance.write(from, from_balance - value); - emit!("Burn", value); + emit!("Burn", idx from, value); true } From c48a335b9515112d1d2eab3fd27c2bb3380909eb Mon Sep 17 00:00:00 2001 From: scab24 Date: Mon, 18 Nov 2024 16:04:12 +0100 Subject: [PATCH 3/3] Syscall Log-Implementation of #[indexed] in events --- contract-derive/src/lib.rs | 149 ++++++++---------- erc20/src/lib.rs | 56 ++++++- r55/src/main.rs | 305 ------------------------------------- 3 files changed, 115 insertions(+), 395 deletions(-) diff --git a/contract-derive/src/lib.rs b/contract-derive/src/lib.rs index 68c42e6..83f4c37 100644 --- a/contract-derive/src/lib.rs +++ b/contract-derive/src/lib.rs @@ -2,9 +2,44 @@ extern crate proc_macro; use alloy_core::primitives::keccak256; use proc_macro::TokenStream; use quote::{format_ident, quote}; -use syn::{parse_macro_input, ImplItem, ItemImpl}; +use syn::{parse_macro_input, ImplItem, ItemImpl, DeriveInput, Data, Fields}; use syn::{FnArg, ReturnType}; +#[proc_macro_derive(Event, attributes(indexed))] +pub fn event_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let name = &input.ident; + + let fields = if let Data::Struct(data) = &input.data { + if let Fields::Named(fields) = &data.fields { + &fields.named + } else { + panic!("Event must have named fields"); + } + } else { + panic!("Event must be a struct"); + }; + + let indexed_fields: Vec<_> = fields + .iter() + .filter(|f| { + f.attrs.iter().any(|attr| attr.path.is_ident("indexed")) + }) + .map(|f| &f.ident) + .collect(); + + let expanded = quote! { + impl #name { + const NAME: &'static str = stringify!(#name); + const INDEXED_FIELDS: &'static [&'static str] = &[ + #(stringify!(#indexed_fields)),* + ]; + } + }; + + TokenStream::from(expanded) +} + #[proc_macro_attribute] pub fn show_streams(attr: TokenStream, item: TokenStream) -> TokenStream { println!("attr: \"{}\"", attr.to_string()); @@ -93,27 +128,27 @@ pub fn contract(_attr: TokenStream, item: TokenStream) -> TokenStream { ($arg:expr) => { match stringify!($arg) { // Address - s if s.contains("Address") || s.contains("address") => b"address", - + s if s.contains("Address") => b"address", + // Unsigned integers s if s.contains("u8") => b"uint8", s if s.contains("u16") => b"uint16", s if s.contains("u32") => b"uint32", s if s.contains("u64") => b"uint64", s if s.contains("u128") => b"uint128", - s if s.contains("U256") || s.contains("uint256") => b"uint256", - + s if s.contains("U256") => b"uint256", + // Signed integers s if s.contains("i8") => b"int8", s if s.contains("i16") => b"int16", s if s.contains("i32") => b"int32", s if s.contains("i64") => b"int64", s if s.contains("i128") => b"int128", - s if s.contains("I256") || s.contains("int256") => b"int256", - + s if s.contains("I256") => b"int256", + // Boolean s if s.contains("bool") => b"bool", - + // Bytes y FixedBytes s if s.contains("B256") => b"bytes32", s if s.contains("[u8; 32]") => b"bytes32", @@ -122,28 +157,28 @@ pub fn contract(_attr: TokenStream, item: TokenStream) -> TokenStream { s if s.contains("[u8; 8]") => b"bytes8", s if s.contains("[u8; 4]") => b"bytes4", s if s.contains("[u8; 1]") => b"bytes1", - + // Dynamic bytes & strings s if s.contains("Vec") => b"bytes", - s if s.contains("String") || s.contains("str") => b"string", - + s if s.contains("String") => b"string", + // Dynamic arrays s if s.contains("Vec
") => b"address[]", s if s.contains("Vec") => b"uint256[]", s if s.contains("Vec") => b"bool[]", s if s.contains("Vec") => b"bytes32[]", - + // Static arrays s if s.contains("[Address; ") => b"address[]", s if s.contains("[U256; ") => b"uint256[]", s if s.contains("[bool; ") => b"bool[]", s if s.contains("[B256; ") => b"bytes32[]", - + // Tuples s if s.contains("(Address, U256)") => b"(address,uint256)", s if s.contains("(U256, bool)") => b"(uint256,bool)", s if s.contains("(Address, Address)") => b"(address,address)", - + _ => b"uint64", } }; @@ -151,22 +186,34 @@ pub fn contract(_attr: TokenStream, item: TokenStream) -> TokenStream { #[macro_export] macro_rules! emit { - // Handle multiple arguments: emit!(event_name, idx arg1, arg2, idx arg3, ...) - ($event_name:expr, $($val:tt)+) => {{ + ($event:ident, $($field:expr),*) => {{ use alloy_sol_types::SolValue; use alloy_core::primitives::{keccak256, B256, U256, I256}; use alloc::vec::Vec; let mut signature = alloc::vec![]; - signature.extend_from_slice($event_name.as_bytes()); + signature.extend_from_slice($event::NAME.as_bytes()); signature.extend_from_slice(b"("); - // Initialize topics[0] for signature hash let mut first = true; let mut topics = alloc::vec![B256::default()]; let mut data = Vec::new(); - process_args!(signature, first, topics, data, $($val)+); + $( + if !first { signature.extend_from_slice(b","); } + first = false; + + signature.extend_from_slice(get_type_signature!($field)); + let encoded = $field.abi_encode(); + + // Aquí debes mapear $field a el identificador correspondiente en $event + let field_ident = stringify!($field); + if $event::INDEXED_FIELDS.contains(&field_ident) && topics.len() < 4 { + topics.push(B256::from_slice(&encoded)); + } else { + data.extend_from_slice(&encoded); + } + )* signature.extend_from_slice(b")"); topics[0] = B256::from(keccak256(&signature)); @@ -178,68 +225,6 @@ pub fn contract(_attr: TokenStream, item: TokenStream) -> TokenStream { eth_riscv_runtime::emit_log(data.as_ref(), &topics); } }}; - - // Handle single argument: emit!(event_name, arg) - ($event_name:expr, $val:expr) => {{ - use alloy_sol_types::SolValue; - use alloy_core::primitives::{keccak256, B256, U256, I256}; - use alloc::vec::Vec; - - let mut signature = alloc::vec![]; - signature.extend_from_slice($event_name.as_bytes()); - signature.extend_from_slice(b"("); - signature.extend_from_slice(get_type_signature!($val)); - signature.extend_from_slice(b")"); - - let topic0 = B256::from(keccak256(&signature)); - let topics = alloc::vec![topic0]; - - let encoded = $val.abi_encode(); - eth_riscv_runtime::emit_log(&encoded, &topics); - }}; - } - - #[macro_export] - macro_rules! process_args { - // Process final non-indexed value - ($sig:expr, $first:expr, $topics:expr, $data:expr, $val:expr) => {{ - if !$first { $sig.extend_from_slice(b","); } - $sig.extend_from_slice(get_type_signature!($val)); - let encoded = $val.abi_encode(); - $data.extend_from_slice(&encoded); - }}; - - // Process final indexed value (idx) - ($sig:expr, $first:expr, $topics:expr, $data:expr, idx $val:expr) => {{ - if !$first { $sig.extend_from_slice(b","); } - $sig.extend_from_slice(get_type_signature!($val)); - let encoded = $val.abi_encode(); - if $topics.len() < 4 { // EVM limit: max 4 topics - $topics.push(B256::from_slice(&encoded)); - } - }}; - - // Process indexed value recursively - ($sig:expr, $first:expr, $topics:expr, $data:expr, idx $val:expr, $($rest:tt)+) => {{ - if !$first { $sig.extend_from_slice(b","); } - $first = false; - $sig.extend_from_slice(get_type_signature!($val)); - let encoded = $val.abi_encode(); - if $topics.len() < 4 { - $topics.push(B256::from_slice(&encoded)); - } - process_args!($sig, $first, $topics, $data, $($rest)+); - }}; - - // Process non-indexed value recursively - ($sig:expr, $first:expr, $topics:expr, $data:expr, $val:expr, $($rest:tt)+) => {{ - if !$first { $sig.extend_from_slice(b","); } - $first = false; - $sig.extend_from_slice(get_type_signature!($val)); - let encoded = $val.abi_encode(); - $data.extend_from_slice(&encoded); - process_args!($sig, $first, $topics, $data, $($rest)+); - }}; } }; @@ -300,4 +285,4 @@ fn is_payable(method: &syn::ImplItemMethod) -> bool { } false }) -} +} \ No newline at end of file diff --git a/erc20/src/lib.rs b/erc20/src/lib.rs index 6481fb7..8518c68 100644 --- a/erc20/src/lib.rs +++ b/erc20/src/lib.rs @@ -3,7 +3,7 @@ use core::default::Default; -use contract_derive::{contract, payable}; +use contract_derive::{contract, payable, Event}; use eth_riscv_runtime::types::Mapping; use alloy_core::primitives::{Address, address, U256}; @@ -12,6 +12,41 @@ extern crate alloc; #[derive(Default)] pub struct ERC20 { balance: Mapping, + paused: bool, + metadata: [u8; 32], +} + +#[derive(Event)] +pub struct Transfer { + #[indexed] + pub from: Address, + #[indexed] + pub to: Address, + pub value: u64 +} + +#[derive(Event)] +pub struct Mint { + #[indexed] + pub to: Address, + pub value: u64 +} + +#[derive(Event)] +pub struct Burn { + #[indexed] + pub from: Address, + pub value: u64 +} + +#[derive(Event)] +pub struct PauseChanged { + pub paused: bool +} + +#[derive(Event)] +pub struct MetadataUpdated { + pub data: [u8; 32] } #[contract] @@ -20,7 +55,8 @@ impl ERC20 { self.balance.read(owner) } - pub fn transfer(&self, from: Address, to: Address, value: u64) -> bool { + pub fn transfer(&self, to: Address, value: u64) -> bool { + let from = msg_sender(); let from_balance = self.balance.read(from); let to_balance = self.balance.read(to); @@ -31,7 +67,7 @@ impl ERC20 { self.balance.write(from, from_balance - value); self.balance.write(to, to_balance + value); - emit!("Transfer", idx from, idx to, value); + emit!(Transfer, from, to, value); true } @@ -44,7 +80,8 @@ impl ERC20 { let to_balance = self.balance.read(to); self.balance.write(to, to_balance + value); - emit!("Mint", idx to, value); + + emit!(Mint, to, value); true } @@ -55,17 +92,20 @@ impl ERC20 { } self.balance.write(from, from_balance - value); - emit!("Burn", idx from, value); + + emit!(Burn, from, value); true } pub fn set_paused(&self, paused: bool) -> bool { - emit!("PauseChanged", paused); + + emit!(PauseChanged, paused); true } pub fn update_metadata(&self, data: [u8; 32]) -> bool { - emit!("MetadataUpdated", data); + + emit!(MetadataUpdated, data); true } -} +} \ No newline at end of file diff --git a/r55/src/main.rs b/r55/src/main.rs index 7758dc6..384aaa1 100644 --- a/r55/src/main.rs +++ b/r55/src/main.rs @@ -203,312 +203,7 @@ fn parse_bool_result(hex_result: &[u8]) -> bool { } } -fn test_erc20_mint_and_transfer() { - let rv_bytecode = compile_runtime("erc20").unwrap(); - println!("Bytecode length: {}", rv_bytecode.len()); - - const CONTRACT_ADDR: Address = address!("0d4a11d5EEaaC28EC3F61d100daF4d40471f1852"); - let mut db = InMemoryDB::default(); - - let mut bytecode = vec![0xff]; - bytecode.extend_from_slice(&rv_bytecode); - let bytecode = Bytes::from(bytecode); - add_contract_to_db(&mut db, CONTRACT_ADDR, bytecode); - - let selector_balance = &keccak256("balance_of")[0..4]; - let selector_transfer = &keccak256("transfer")[0..4]; - let selector_mint = &keccak256("mint")[0..4]; - - let address1: Address = address!("0000000000000000000000000000000000000001"); - let address2: Address = address!("0000000000000000000000000000000000000002"); - - // Mint 100 tokens - let mint_value: u64 = 100; - let mut calldata_mint = (address1, mint_value).abi_encode(); - let mut complete_calldata_mint = selector_mint.to_vec(); - complete_calldata_mint.append(&mut calldata_mint); - - println!("\n=== Minting tokens ==="); - println!("Minting {} tokens to address1...", mint_value); - let mint_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_mint); - println!("Mint tx result: {}", parse_bool_result(&mint_result.output)); - - // Print mint logs - println!("\nMint Event Logs:"); - for log in mint_result.logs { - println!(" Event Signature: 0x{}", hex::encode(&log.topics()[0])); - if log.topics().len() > 1 { - println!(" Address: 0x{}", hex::encode(&log.topics()[1])); - } - println!(" Data: 0x{}", hex::encode(&log.data.data)); - } - - // Check initial balance - let mut calldata_balance = address1.abi_encode(); - let mut complete_calldata_balance = selector_balance.to_vec(); - complete_calldata_balance.append(&mut calldata_balance); - - println!("\n=== Checking initial balance ==="); - let balance_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_balance.clone()); - let initial_balance = parse_hex_result(&balance_result.output); - println!("Address1 balance: {} (hex: 0x{})", initial_balance, hex::encode(&balance_result.output)); - assert_eq!(initial_balance, mint_value, "Initial balance should be {}", mint_value); - - // Transfer 30 tokens - let transfer_value: u64 = 30; - let mut calldata_transfer = (address1, address2, transfer_value).abi_encode(); - let mut complete_calldata_transfer = selector_transfer.to_vec(); - complete_calldata_transfer.append(&mut calldata_transfer); - - println!("\n=== Transferring tokens ==="); - println!("Transferring {} tokens from address1 to address2...", transfer_value); - let transfer_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_transfer); - println!("Transfer tx result: {}", parse_bool_result(&transfer_result.output)); - - // Print transfer logs - println!("\nTransfer Event Logs:"); - for log in transfer_result.logs { - println!(" Event Signature: 0x{}", hex::encode(&log.topics()[0])); - for (i, topic) in log.topics().iter().enumerate().skip(1) { - println!(" Topic {}: 0x{}", i, hex::encode(topic)); - } - println!(" Data: 0x{}", hex::encode(&log.data.data)); - } - - // Check final balances - println!("\n=== Checking final balances ==="); - let balance_result1 = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_balance.clone()); - let final_balance1 = parse_hex_result(&balance_result1.output); - println!("Address1 final balance: {} (hex: 0x{})", final_balance1, hex::encode(&balance_result1.output)); - assert_eq!(final_balance1, mint_value - transfer_value, - "Address1 should have {} tokens", mint_value - transfer_value); - - let mut calldata_balance2 = address2.abi_encode(); - let mut complete_calldata_balance2 = selector_balance.to_vec(); - complete_calldata_balance2.append(&mut calldata_balance2); - - let balance_result2 = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_balance2); - let final_balance2 = parse_hex_result(&balance_result2.output); - println!("Address2 final balance: {} (hex: 0x{})", final_balance2, hex::encode(&balance_result2.output)); - assert_eq!(final_balance2, transfer_value, - "Address2 should have {} tokens", transfer_value); - - println!("\n=== Test Summary ==="); - println!("✓ Initial mint: {} tokens to address1", mint_value); - println!("✓ Initial balance of address1: {} tokens", initial_balance); - println!("✓ Transfer: {} tokens from address1 to address2", transfer_value); - println!("✓ Final balance of address1: {} tokens", final_balance1); - println!("✓ Final balance of address2: {} tokens", final_balance2); -} - -fn test_transfer_event_values() { - let rv_bytecode = compile_runtime("erc20").unwrap(); - const CONTRACT_ADDR: Address = address!("0d4a11d5EEaaC28EC3F61d100daF4d40471f1852"); - let mut db = InMemoryDB::default(); - - let mut bytecode = vec![0xff]; - bytecode.extend_from_slice(&rv_bytecode); - let bytecode = Bytes::from(bytecode); - add_contract_to_db(&mut db, CONTRACT_ADDR, bytecode); - - let from_addr = address!("0000000000000000000000000000000000000001"); - let to_addr = address!("0000000000000000000000000000000000000002"); - let mint_amount = 1000u64; - let transfer_amount = 500u64; - - // Mint - let selector_mint = &keccak256("mint")[0..4]; - let mut calldata_mint = (from_addr, mint_amount).abi_encode(); - let mut complete_calldata_mint = selector_mint.to_vec(); - complete_calldata_mint.append(&mut calldata_mint); - let mint_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_mint); - assert!(parse_bool_result(&mint_result.output), "Mint failed"); - - // Transfer - let selector_transfer = &keccak256("transfer")[0..4]; - let mut calldata_transfer = (from_addr, to_addr, transfer_amount).abi_encode(); - let mut complete_calldata_transfer = selector_transfer.to_vec(); - complete_calldata_transfer.append(&mut calldata_transfer); - let transfer_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_transfer); - - // Print events - let log = &transfer_result.logs[0]; - let log_mint = &mint_result.logs[0]; - - println!("\n=== Transfer Event - Expected vs Actual Values ==="); - println!("Contract Address:"); - println!(" Expected: {:?}", CONTRACT_ADDR); - println!(" Actual : {:?}", log.address); - println!(); - - println!("From Address:"); - println!(" Expected: {:?}", from_addr); - println!(" Actual : 0x{}", hex::encode(&log.topics()[1])); - println!(); - - println!("To Address:"); - println!(" Expected: {:?}", to_addr); - println!(" Actual : 0x{}", hex::encode(&log.topics()[2])); - println!(); - - println!("Transfer Amount:"); - println!(" Expected: {} tokens", transfer_amount); - println!(" Actual : {} tokens", parse_hex_result(&log.data.data)); - - println!("\n=== MINT Event - Expected vs Actual Values ==="); - println!("Contract Address:"); - println!(" Expected: {:?}", CONTRACT_ADDR); - println!(" Actual : {:?}", log_mint.address); - println!(); - - println!("TO Address:"); - println!(" Expected: {:?}", from_addr); - println!(" Actual : 0x{}", hex::encode(&log_mint.topics()[1])); - println!(); - - println!("Transfer Amount:"); - println!(" Expected: {} tokens", mint_amount); - println!(" Actual : {} tokens", parse_hex_result(&log_mint.data.data)); - - assert_eq!(log.address, CONTRACT_ADDR, "Contract address mismatch"); - assert_eq!(parse_hex_result(&log.data.data), transfer_amount, "Transfer amount mismatch"); -} - -fn test_burn_event() { - let rv_bytecode = compile_runtime("erc20").unwrap(); - const CONTRACT_ADDR: Address = address!("0d4a11d5EEaaC28EC3F61d100daF4d40471f1852"); - let mut db = InMemoryDB::default(); - - let mut bytecode = vec![0xff]; - bytecode.extend_from_slice(&rv_bytecode); - let bytecode = Bytes::from(bytecode); - add_contract_to_db(&mut db, CONTRACT_ADDR, bytecode); - - let address1 = address!("0000000000000000000000000000000000000001"); - let burn_amount = 500u64; - - // Mint - let selector_mint = &keccak256("mint")[0..4]; - let mut calldata_mint = (address1, 1000u64).abi_encode(); - let mut complete_calldata_mint = selector_mint.to_vec(); - complete_calldata_mint.append(&mut calldata_mint); - run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_mint); - - // Burn - let selector_burn = &keccak256("burn")[0..4]; - let mut calldata_burn = (address1, burn_amount).abi_encode(); - let mut complete_calldata_burn = selector_burn.to_vec(); - complete_calldata_burn.append(&mut calldata_burn); - - let burn_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_burn); - let log = &burn_result.logs[0]; - - println!("\nEmitted Events:"); - println!(" Burn(uint64)"); - println!(" value: {} (input: {})", parse_hex_result(&log.data.data), burn_amount); - - assert_eq!(parse_hex_result(&log.data.data), burn_amount, "Event value doesn't match input value"); -} - -fn test_pause_event() { - let rv_bytecode = compile_runtime("erc20").unwrap(); - const CONTRACT_ADDR: Address = address!("0d4a11d5EEaaC28EC3F61d100daF4d40471f1852"); - let mut db = InMemoryDB::default(); - - let mut bytecode = vec![0xff]; - bytecode.extend_from_slice(&rv_bytecode); - let bytecode = Bytes::from(bytecode); - add_contract_to_db(&mut db, CONTRACT_ADDR, bytecode); - - let pause_state = true; - let selector_pause = &keccak256("set_paused")[0..4]; - let mut calldata_pause = pause_state.abi_encode(); - let mut complete_calldata_pause = selector_pause.to_vec(); - complete_calldata_pause.append(&mut calldata_pause); - - let pause_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_pause); - let log = &pause_result.logs[0]; - - println!("\nEmitted Events:"); - println!(" PauseChanged(bool)"); - println!(" paused: {} (input: {})", parse_bool_result(&log.data.data), pause_state); - - assert_eq!(parse_bool_result(&log.data.data), pause_state, "Wrong pause state"); -} - -fn test_metadata_event() { - let rv_bytecode = compile_runtime("erc20").unwrap(); - const CONTRACT_ADDR: Address = address!("0d4a11d5EEaaC28EC3F61d100daF4d40471f1852"); - let mut db = InMemoryDB::default(); - - let mut bytecode = vec![0xff]; - bytecode.extend_from_slice(&rv_bytecode); - let bytecode = Bytes::from(bytecode); - add_contract_to_db(&mut db, CONTRACT_ADDR, bytecode); - - let metadata: [u8; 32] = [1u8; 32]; - let selector_metadata = &keccak256("update_metadata")[0..4]; - let mut calldata_metadata = metadata.abi_encode(); - let mut complete_calldata_metadata = selector_metadata.to_vec(); - complete_calldata_metadata.append(&mut calldata_metadata); - - let metadata_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_metadata); - let log = &metadata_result.logs[0]; - - println!("\nEmitted Events:"); - println!(" MetadataUpdated(bytes32)"); - println!(" data: 0x{} (input: 0x{})", hex::encode(&log.data.data), hex::encode(&metadata)); - - assert_eq!(log.data.data.as_ref(), &metadata[..], "Wrong metadata"); -} - -fn test_transfer_event() { - let rv_bytecode = compile_runtime("erc20").unwrap(); - const CONTRACT_ADDR: Address = address!("0d4a11d5EEaaC28EC3F61d100daF4d40471f1852"); - let mut db = InMemoryDB::default(); - - let mut bytecode = vec![0xff]; - bytecode.extend_from_slice(&rv_bytecode); - let bytecode = Bytes::from(bytecode); - add_contract_to_db(&mut db, CONTRACT_ADDR, bytecode); - - let from = address!("0000000000000000000000000000000000000001"); - let to = address!("0000000000000000000000000000000000000002"); - let amount = 1000u64; - - // Mint - let selector_mint = &keccak256("mint")[0..4]; - let mut calldata_mint = (from, amount * 2).abi_encode(); - let mut complete_calldata_mint = selector_mint.to_vec(); - complete_calldata_mint.append(&mut calldata_mint); - run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_mint); - - // Transfer - let selector_transfer = &keccak256("transfer")[0..4]; - let mut calldata_transfer = (from, to, amount).abi_encode(); - let mut complete_calldata_transfer = selector_transfer.to_vec(); - complete_calldata_transfer.append(&mut calldata_transfer); - - let transfer_result = run_tx(&mut db, &CONTRACT_ADDR, complete_calldata_transfer); - let log = &transfer_result.logs[0]; - - println!("\nEmitted Events:"); - println!(" Transfer(address,address,uint64)"); - println!(" from : {} (input: {})", hex::encode(&log.topics()[1]), hex::encode(from)); - println!(" to : {} (input: {})", hex::encode(&log.topics()[2]), hex::encode(to)); - println!(" value: {} (input: {})", parse_hex_result(&log.data.data), amount); - - assert_eq!(Address::from_slice(&log.topics()[1][12..]), from, "Wrong from address"); - assert_eq!(Address::from_slice(&log.topics()[2][12..]), to, "Wrong to address"); - assert_eq!(parse_hex_result(&log.data.data), amount, "Wrong transfer amount"); -} fn main() { test_runtime_from_binary(); test_deploy(); - test_erc20_mint_and_transfer(); - test_transfer_event_values(); - test_burn_event(); - test_pause_event(); - test_metadata_event(); - test_transfer_event(); }