From 2b9e40225d0ea0b344b75e8c7dd93207518bc7f7 Mon Sep 17 00:00:00 2001 From: xermicus Date: Tue, 19 Mar 2024 10:49:47 +0100 Subject: [PATCH] implement calldatacopy Signed-off-by: xermicus --- .gitignore | 2 + Cargo.lock | 57 ++++++++++++++++++- Cargo.toml | 1 + README.md | 5 +- crates/integration/Cargo.toml | 3 +- crates/integration/src/lib.rs | 48 +++++++++++++--- crates/integration/src/mock_runtime.rs | 23 +++++++- crates/llvm-context/src/eravm/context/mod.rs | 17 +++--- .../llvm-context/src/eravm/context/pointer.rs | 1 + crates/llvm-context/src/eravm/evm/calldata.rs | 39 +++++++------ crates/llvm-context/src/eravm/evm/crypto.rs | 48 ++++++++++++++-- .../src/polkavm_guest.c | 9 +++ 12 files changed, 210 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index 2598164..7d6b24e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ .DS_Store /*.sol /*.yul +/*.ll +/*.s diff --git a/Cargo.lock b/Cargo.lock index 1c1492d..4e695c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,36 @@ dependencies = [ "bytes", ] +[[package]] +name = "alloy-sol-macro" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86ec0a47740b20bc5613b8712d0d321d031c4efc58e9645af96085d5cccfc27" +dependencies = [ + "const-hex", + "dunce", + "heck 0.4.1", + "indexmap", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.52", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad09ec5853fa700d12d778ad224dcdec636af424d29fad84fb9a2f16a5b0ef09" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", + "serde", +] + [[package]] name = "anyhow" version = "1.0.81" @@ -470,6 +500,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + [[package]] name = "ecdsa" version = "0.16.9" @@ -711,6 +747,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hex" version = "0.4.3" @@ -1378,6 +1420,7 @@ name = "revive-integration" version = "0.1.0" dependencies = [ "alloy-primitives", + "alloy-sol-types", "era-compiler-llvm-context", "hex", "parity-scale-codec", @@ -1737,7 +1780,7 @@ version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ - "heck", + "heck 0.3.3", "proc-macro-error", "proc-macro2", "quote", @@ -1772,6 +1815,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3d0961cd53c23ea94eeec56ba940f636f6394788976e9f16ca5ee0aca7464a" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.52", +] + [[package]] name = "tap" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 79dcb9c..568b4fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ polkavm-linker = { git = "https://github.com/koute/polkavm.git" } polkavm = { git = "https://github.com/koute/polkavm.git" } parity-scale-codec = "3.6" alloy-primitives = "0.6" +alloy-sol-types = "0.6" [workspace.dependencies.inkwell] git = "https://github.com/TheDan64/inkwell.git" diff --git a/README.md b/README.md index b7c3671..fc4dc84 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ YUL and EVM bytecode recompiler to LLVM, targetting RISC-V on PolkaVM. -Code bases of [frontend](https://github.com/matter-labs/era-compiler-solidity) and [code generator](https://github.com/matter-labs/era-compiler-llvm-context) are forked adapted from ZKSync `zksolc`. +Code bases of [frontend](https://github.com/matter-labs/era-compiler-solidity) and [code generator](https://github.com/matter-labs/era-compiler-llvm-context) are forked and adapted from ZKSync `zksolc`. # Status @@ -12,8 +12,9 @@ Currently, primary goal of this codebase is to allow for benchmarks comparing pe The project is in a very early PoC phase. Don't yet expect the produced code to be working nor to be correct for anything more than a basic flipper contract at the current stage. -- [ ] Efficient implementations of byte swaps, memset, memmove and the like +- [ ] Efficient implementations of byte swaps, memset, memmove, mulmod and the like - [ ] Use `drink` for integration tests once we have 64bit support in PolkaVM +- [ ] Use PolkaVM allocator for heap space - [ ] Exercice `schlau` and possibly `smart-bench` benchmark cases - [ ] Tests currently rely on the binary being in $PATH, which is very annoying and requires `cargo install` all the times - [ ] Define how to do deployments diff --git a/crates/integration/Cargo.toml b/crates/integration/Cargo.toml index 5396bd4..0c31c40 100644 --- a/crates/integration/Cargo.toml +++ b/crates/integration/Cargo.toml @@ -11,4 +11,5 @@ polkavm = { workspace = true } parity-scale-codec = { workspace = true } revive-solidity = { path = "../solidity" } era-compiler-llvm-context = { path = "../llvm-context" } -alloy-primitives = { workspace = true } \ No newline at end of file +alloy-primitives = { workspace = true } +alloy-sol-types = { workspace = true } diff --git a/crates/integration/src/lib.rs b/crates/integration/src/lib.rs index d7eac82..ca2a618 100644 --- a/crates/integration/src/lib.rs +++ b/crates/integration/src/lib.rs @@ -27,7 +27,8 @@ pub fn compile_blob(contract_name: &str, source_code: &str) -> Vec { #[cfg(test)] mod tests { - use alloy_primitives::U256; + use alloy_primitives::{FixedBytes, Keccak256, I256, U256}; + use alloy_sol_types::{sol, SolCall}; use crate::mock_runtime::{self, State}; @@ -46,6 +47,37 @@ mod tests { assert_eq!(state.storage[&U256::ZERO], U256::ZERO); } + #[test] + fn hash_keccak_256() { + sol!( + #[derive(Debug, PartialEq, Eq)] + contract TestSha3 { + function test(string memory _pre) external payable returns (bytes32); + } + ); + let source = r#"contract TestSha3 { + function test(string memory _pre) external payable returns (bytes32 hash) { + hash = keccak256(bytes(_pre)); + } + }"#; + let code = crate::compile_blob("TestSha3", source); + + let param = "hello"; + let input = TestSha3::testCall::new((param.to_string(),)).abi_encode(); + + let state = State::new(input); + let (instance, export) = mock_runtime::prepare(&code); + let state = crate::mock_runtime::call(state, &instance, export); + + assert_eq!(state.output.flags, 0); + + let mut hasher = Keccak256::new(); + hasher.update(param); + let expected = hasher.finalize(); + let received = FixedBytes::<32>::from_slice(&state.output.data); + assert_eq!(expected, received); + } + #[test] fn erc20() { let _ = crate::compile_blob("ERC20", include_str!("../contracts/ERC20.sol")); @@ -54,8 +86,8 @@ mod tests { #[test] fn triangle_number() { let code = crate::compile_blob("Computation", include_str!("../contracts/Computation.sol")); - let param = alloy_primitives::U256::try_from(13).unwrap(); - let expected = alloy_primitives::U256::try_from(91).unwrap(); + let param = U256::try_from(13).unwrap(); + let expected = U256::try_from(91).unwrap(); // function triangle_number(int64) let mut input = 0x0f760610u32.to_be_bytes().to_vec(); @@ -67,16 +99,15 @@ mod tests { assert_eq!(state.output.flags, 0); - let received = - alloy_primitives::U256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); + let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); assert_eq!(received, expected); } #[test] fn odd_product() { let code = crate::compile_blob("Computation", include_str!("../contracts/Computation.sol")); - let param = alloy_primitives::I256::try_from(5i32).unwrap(); - let expected = alloy_primitives::I256::try_from(945i64).unwrap(); + let param = I256::try_from(5i32).unwrap(); + let expected = I256::try_from(945i64).unwrap(); // function odd_product(int32) let mut input = 0x00261b66u32.to_be_bytes().to_vec(); @@ -88,8 +119,7 @@ mod tests { assert_eq!(state.output.flags, 0); - let received = - alloy_primitives::I256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); + let received = I256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); assert_eq!(received, expected); } } diff --git a/crates/integration/src/mock_runtime.rs b/crates/integration/src/mock_runtime.rs index 4d7d716..1110d4e 100644 --- a/crates/integration/src/mock_runtime.rs +++ b/crates/integration/src/mock_runtime.rs @@ -2,7 +2,7 @@ //! TODO: Switch to drink! once RISCV is ready in polkadot-sdk use std::collections::HashMap; -use alloy_primitives::U256; +use alloy_primitives::{Keccak256, U256}; use parity_scale_codec::Encode; use polkavm::{ Caller, Config, Engine, ExportIndex, GasMeteringKind, InstancePre, Linker, Module, @@ -168,6 +168,27 @@ fn link_host_functions(engine: &Engine) -> Linker { ) .unwrap(); + linker + .func_wrap( + "hash_keccak_256", + |caller: Caller, + input_ptr: u32, + input_len: u32, + out_ptr: u32| + -> Result<(), Trap> { + let (mut caller, _) = caller.split(); + + let pre = caller.read_memory_into_vec(input_ptr, input_len)?; + + let mut hasher = Keccak256::new(); + hasher.update(&pre); + caller.write_memory(out_ptr, &hasher.finalize()[..])?; + + Ok(()) + }, + ) + .unwrap(); + linker } diff --git a/crates/llvm-context/src/eravm/context/mod.rs b/crates/llvm-context/src/eravm/context/mod.rs index ba6f13b..e73955c 100644 --- a/crates/llvm-context/src/eravm/context/mod.rs +++ b/crates/llvm-context/src/eravm/context/mod.rs @@ -671,15 +671,13 @@ where "offset_ptrtoint", )?; let pointer_value = unsafe { - self.builder - .build_gep( - self.byte_type(), - heap_pointer.value.as_pointer_value(), - &[offset], - "heap_offset_via_gep", - ) - .unwrap() - }; + self.builder.build_gep( + self.byte_type(), + heap_pointer.value.as_pointer_value(), + &[offset], + "heap_offset_via_gep", + ) + }?; let value = self .builder() .build_load(pointer.r#type, pointer_value, name)?; @@ -806,6 +804,7 @@ where ) .unwrap() }; + let value = self.build_byte_swap(value.as_basic_value_enum()); let instruction = self.builder.build_store(pointer_value, value).unwrap(); diff --git a/crates/llvm-context/src/eravm/context/pointer.rs b/crates/llvm-context/src/eravm/context/pointer.rs index 071cb55..d5595fa 100644 --- a/crates/llvm-context/src/eravm/context/pointer.rs +++ b/crates/llvm-context/src/eravm/context/pointer.rs @@ -78,6 +78,7 @@ impl<'ctx> Pointer<'ctx> { "Stack pointers cannot be addressed" ); + let offset = context.safe_truncate_int_to_i32(offset).unwrap(); let value = context .builder .build_int_to_ptr( diff --git a/crates/llvm-context/src/eravm/evm/calldata.rs b/crates/llvm-context/src/eravm/evm/calldata.rs index 273a62e..2117b5c 100644 --- a/crates/llvm-context/src/eravm/evm/calldata.rs +++ b/crates/llvm-context/src/eravm/evm/calldata.rs @@ -59,29 +59,36 @@ pub fn copy<'ctx, D>( where D: Dependency + Clone, { - // TODO: Untested - let destination = Pointer::new_with_offset( - context, - AddressSpace::Heap, - context.byte_type(), - destination_offset, - "calldata_copy_destination_pointer", - ); + let heap_pointer = context + .get_global(crate::eravm::GLOBAL_HEAP_MEMORY_POINTER)? + .value + .as_pointer_value(); + let destination = unsafe { + context.builder().build_gep( + context.byte_type(), + heap_pointer, + &[destination_offset], + "calldata_pointer_with_offset", + ) + }?; + let calldata_pointer = context .get_global(crate::eravm::GLOBAL_CALLDATA_POINTER)? .value .as_pointer_value(); - let source = context.build_gep( - Pointer::new(context.byte_type(), AddressSpace::Stack, calldata_pointer), - &[source_offset], - context.field_type().as_basic_type_enum(), - "calldata_pointer_with_offset", - ); + let source = unsafe { + context.builder().build_gep( + context.byte_type(), + calldata_pointer, + &[source_offset], + "calldata_pointer_with_offset", + ) + }?; context.build_memcpy( context.intrinsics().memory_copy_from_generic, - destination, - source, + Pointer::new(context.byte_type(), AddressSpace::Stack, destination), + Pointer::new(context.byte_type(), AddressSpace::Stack, source), size, "calldata_copy_memcpy_from_child", ) diff --git a/crates/llvm-context/src/eravm/evm/crypto.rs b/crates/llvm-context/src/eravm/evm/crypto.rs index 97761b3..d29a7d2 100644 --- a/crates/llvm-context/src/eravm/evm/crypto.rs +++ b/crates/llvm-context/src/eravm/evm/crypto.rs @@ -9,12 +9,52 @@ use crate::eravm::Dependency; /// Translates the `sha3` instruction. /// pub fn sha3<'ctx, D>( - _context: &mut Context<'ctx, D>, - _offset: inkwell::values::IntValue<'ctx>, - _length: inkwell::values::IntValue<'ctx>, + context: &mut Context<'ctx, D>, + offset: inkwell::values::IntValue<'ctx>, + length: inkwell::values::IntValue<'ctx>, ) -> anyhow::Result> where D: Dependency + Clone, { - todo!() + let offset_casted = context.safe_truncate_int_to_i32(offset)?; + let heap_pointer = context.get_global(crate::eravm::GLOBAL_HEAP_MEMORY_POINTER)?; + let input_pointer = unsafe { + context.builder().build_gep( + context.byte_type(), + heap_pointer.value.as_pointer_value(), + &[offset_casted], + "heap_offset_via_gep", + ) + }?; + let input_pointer_casted = context.builder().build_ptr_to_int( + input_pointer, + context.integer_type(32), + "input_pointer_casted", + )?; + + let length_casted = context.safe_truncate_int_to_i32(length)?; + + let output_pointer = context.build_alloca(context.field_type(), "output_pointer"); + let output_pointer_casted = context.builder().build_ptr_to_int( + output_pointer.value, + context.integer_type(32), + "output_pointer_casted", + )?; + + let function = context + .module() + .get_function("hash_keccak_256") + .expect("is declared"); + + context.builder().build_call( + function, + &[ + input_pointer_casted.into(), + length_casted.into(), + output_pointer_casted.into(), + ], + "call_seal_hash_keccak_256", + )?; + + Ok(context.build_byte_swap(context.build_load(output_pointer, "sha3_output")?)) } diff --git a/crates/pallet-contracts-pvm-llapi/src/polkavm_guest.c b/crates/pallet-contracts-pvm-llapi/src/polkavm_guest.c index 50f1fa9..0aa0721 100644 --- a/crates/pallet-contracts-pvm-llapi/src/polkavm_guest.c +++ b/crates/pallet-contracts-pvm-llapi/src/polkavm_guest.c @@ -12,6 +12,15 @@ void * memset(void *b, int c, size_t len) { return b; } +void * memcpy(void *dst, const void *_src, size_t len) { + uint8_t *dest = dst; + const uint8_t *src = _src; + + while (len--) *dest++ = *src++; + + return dst; +} + // Exports