mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-06-18 10:31:09 +00:00
@@ -4,3 +4,5 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
/*.sol
|
/*.sol
|
||||||
/*.yul
|
/*.yul
|
||||||
|
/*.ll
|
||||||
|
/*.s
|
||||||
|
|||||||
Generated
+56
-1
@@ -55,6 +55,36 @@ dependencies = [
|
|||||||
"bytes",
|
"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]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.81"
|
version = "1.0.81"
|
||||||
@@ -470,6 +500,12 @@ dependencies = [
|
|||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dunce"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ecdsa"
|
name = "ecdsa"
|
||||||
version = "0.16.9"
|
version = "0.16.9"
|
||||||
@@ -711,6 +747,12 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hex"
|
name = "hex"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
@@ -1378,6 +1420,7 @@ name = "revive-integration"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
|
"alloy-sol-types",
|
||||||
"era-compiler-llvm-context",
|
"era-compiler-llvm-context",
|
||||||
"hex",
|
"hex",
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
@@ -1737,7 +1780,7 @@ version = "0.4.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
|
checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck 0.3.3",
|
||||||
"proc-macro-error",
|
"proc-macro-error",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1772,6 +1815,18 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"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]]
|
[[package]]
|
||||||
name = "tap"
|
name = "tap"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ polkavm-linker = { git = "https://github.com/koute/polkavm.git" }
|
|||||||
polkavm = { git = "https://github.com/koute/polkavm.git" }
|
polkavm = { git = "https://github.com/koute/polkavm.git" }
|
||||||
parity-scale-codec = "3.6"
|
parity-scale-codec = "3.6"
|
||||||
alloy-primitives = "0.6"
|
alloy-primitives = "0.6"
|
||||||
|
alloy-sol-types = "0.6"
|
||||||
|
|
||||||
[workspace.dependencies.inkwell]
|
[workspace.dependencies.inkwell]
|
||||||
git = "https://github.com/TheDan64/inkwell.git"
|
git = "https://github.com/TheDan64/inkwell.git"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
YUL and EVM bytecode recompiler to LLVM, targetting RISC-V on PolkaVM.
|
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
|
# 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.
|
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 `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
|
- [ ] 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
|
- [ ] 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
|
- [ ] Define how to do deployments
|
||||||
|
|||||||
@@ -11,4 +11,5 @@ polkavm = { workspace = true }
|
|||||||
parity-scale-codec = { workspace = true }
|
parity-scale-codec = { workspace = true }
|
||||||
revive-solidity = { path = "../solidity" }
|
revive-solidity = { path = "../solidity" }
|
||||||
era-compiler-llvm-context = { path = "../llvm-context" }
|
era-compiler-llvm-context = { path = "../llvm-context" }
|
||||||
alloy-primitives = { workspace = true }
|
alloy-primitives = { workspace = true }
|
||||||
|
alloy-sol-types = { workspace = true }
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ pub fn compile_blob(contract_name: &str, source_code: &str) -> Vec<u8> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
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};
|
use crate::mock_runtime::{self, State};
|
||||||
|
|
||||||
@@ -46,6 +47,37 @@ mod tests {
|
|||||||
assert_eq!(state.storage[&U256::ZERO], U256::ZERO);
|
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]
|
#[test]
|
||||||
fn erc20() {
|
fn erc20() {
|
||||||
let _ = crate::compile_blob("ERC20", include_str!("../contracts/ERC20.sol"));
|
let _ = crate::compile_blob("ERC20", include_str!("../contracts/ERC20.sol"));
|
||||||
@@ -54,8 +86,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn triangle_number() {
|
fn triangle_number() {
|
||||||
let code = crate::compile_blob("Computation", include_str!("../contracts/Computation.sol"));
|
let code = crate::compile_blob("Computation", include_str!("../contracts/Computation.sol"));
|
||||||
let param = alloy_primitives::U256::try_from(13).unwrap();
|
let param = U256::try_from(13).unwrap();
|
||||||
let expected = alloy_primitives::U256::try_from(91).unwrap();
|
let expected = U256::try_from(91).unwrap();
|
||||||
|
|
||||||
// function triangle_number(int64)
|
// function triangle_number(int64)
|
||||||
let mut input = 0x0f760610u32.to_be_bytes().to_vec();
|
let mut input = 0x0f760610u32.to_be_bytes().to_vec();
|
||||||
@@ -67,16 +99,15 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(state.output.flags, 0);
|
assert_eq!(state.output.flags, 0);
|
||||||
|
|
||||||
let received =
|
let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
||||||
alloy_primitives::U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
|
||||||
assert_eq!(received, expected);
|
assert_eq!(received, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn odd_product() {
|
fn odd_product() {
|
||||||
let code = crate::compile_blob("Computation", include_str!("../contracts/Computation.sol"));
|
let code = crate::compile_blob("Computation", include_str!("../contracts/Computation.sol"));
|
||||||
let param = alloy_primitives::I256::try_from(5i32).unwrap();
|
let param = I256::try_from(5i32).unwrap();
|
||||||
let expected = alloy_primitives::I256::try_from(945i64).unwrap();
|
let expected = I256::try_from(945i64).unwrap();
|
||||||
|
|
||||||
// function odd_product(int32)
|
// function odd_product(int32)
|
||||||
let mut input = 0x00261b66u32.to_be_bytes().to_vec();
|
let mut input = 0x00261b66u32.to_be_bytes().to_vec();
|
||||||
@@ -88,8 +119,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(state.output.flags, 0);
|
assert_eq!(state.output.flags, 0);
|
||||||
|
|
||||||
let received =
|
let received = I256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
||||||
alloy_primitives::I256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
|
|
||||||
assert_eq!(received, expected);
|
assert_eq!(received, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
//! TODO: Switch to drink! once RISCV is ready in polkadot-sdk
|
//! TODO: Switch to drink! once RISCV is ready in polkadot-sdk
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use alloy_primitives::U256;
|
use alloy_primitives::{Keccak256, U256};
|
||||||
use parity_scale_codec::Encode;
|
use parity_scale_codec::Encode;
|
||||||
use polkavm::{
|
use polkavm::{
|
||||||
Caller, Config, Engine, ExportIndex, GasMeteringKind, InstancePre, Linker, Module,
|
Caller, Config, Engine, ExportIndex, GasMeteringKind, InstancePre, Linker, Module,
|
||||||
@@ -168,6 +168,27 @@ fn link_host_functions(engine: &Engine) -> Linker<State> {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
linker
|
||||||
|
.func_wrap(
|
||||||
|
"hash_keccak_256",
|
||||||
|
|caller: Caller<State>,
|
||||||
|
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
|
linker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -671,15 +671,13 @@ where
|
|||||||
"offset_ptrtoint",
|
"offset_ptrtoint",
|
||||||
)?;
|
)?;
|
||||||
let pointer_value = unsafe {
|
let pointer_value = unsafe {
|
||||||
self.builder
|
self.builder.build_gep(
|
||||||
.build_gep(
|
self.byte_type(),
|
||||||
self.byte_type(),
|
heap_pointer.value.as_pointer_value(),
|
||||||
heap_pointer.value.as_pointer_value(),
|
&[offset],
|
||||||
&[offset],
|
"heap_offset_via_gep",
|
||||||
"heap_offset_via_gep",
|
)
|
||||||
)
|
}?;
|
||||||
.unwrap()
|
|
||||||
};
|
|
||||||
let value = self
|
let value = self
|
||||||
.builder()
|
.builder()
|
||||||
.build_load(pointer.r#type, pointer_value, name)?;
|
.build_load(pointer.r#type, pointer_value, name)?;
|
||||||
@@ -806,6 +804,7 @@ where
|
|||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let value = self.build_byte_swap(value.as_basic_value_enum());
|
let value = self.build_byte_swap(value.as_basic_value_enum());
|
||||||
|
|
||||||
let instruction = self.builder.build_store(pointer_value, value).unwrap();
|
let instruction = self.builder.build_store(pointer_value, value).unwrap();
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ impl<'ctx> Pointer<'ctx> {
|
|||||||
"Stack pointers cannot be addressed"
|
"Stack pointers cannot be addressed"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let offset = context.safe_truncate_int_to_i32(offset).unwrap();
|
||||||
let value = context
|
let value = context
|
||||||
.builder
|
.builder
|
||||||
.build_int_to_ptr(
|
.build_int_to_ptr(
|
||||||
|
|||||||
@@ -59,29 +59,36 @@ pub fn copy<'ctx, D>(
|
|||||||
where
|
where
|
||||||
D: Dependency + Clone,
|
D: Dependency + Clone,
|
||||||
{
|
{
|
||||||
// TODO: Untested
|
let heap_pointer = context
|
||||||
let destination = Pointer::new_with_offset(
|
.get_global(crate::eravm::GLOBAL_HEAP_MEMORY_POINTER)?
|
||||||
context,
|
.value
|
||||||
AddressSpace::Heap,
|
.as_pointer_value();
|
||||||
context.byte_type(),
|
let destination = unsafe {
|
||||||
destination_offset,
|
context.builder().build_gep(
|
||||||
"calldata_copy_destination_pointer",
|
context.byte_type(),
|
||||||
);
|
heap_pointer,
|
||||||
|
&[destination_offset],
|
||||||
|
"calldata_pointer_with_offset",
|
||||||
|
)
|
||||||
|
}?;
|
||||||
|
|
||||||
let calldata_pointer = context
|
let calldata_pointer = context
|
||||||
.get_global(crate::eravm::GLOBAL_CALLDATA_POINTER)?
|
.get_global(crate::eravm::GLOBAL_CALLDATA_POINTER)?
|
||||||
.value
|
.value
|
||||||
.as_pointer_value();
|
.as_pointer_value();
|
||||||
let source = context.build_gep(
|
let source = unsafe {
|
||||||
Pointer::new(context.byte_type(), AddressSpace::Stack, calldata_pointer),
|
context.builder().build_gep(
|
||||||
&[source_offset],
|
context.byte_type(),
|
||||||
context.field_type().as_basic_type_enum(),
|
calldata_pointer,
|
||||||
"calldata_pointer_with_offset",
|
&[source_offset],
|
||||||
);
|
"calldata_pointer_with_offset",
|
||||||
|
)
|
||||||
|
}?;
|
||||||
|
|
||||||
context.build_memcpy(
|
context.build_memcpy(
|
||||||
context.intrinsics().memory_copy_from_generic,
|
context.intrinsics().memory_copy_from_generic,
|
||||||
destination,
|
Pointer::new(context.byte_type(), AddressSpace::Stack, destination),
|
||||||
source,
|
Pointer::new(context.byte_type(), AddressSpace::Stack, source),
|
||||||
size,
|
size,
|
||||||
"calldata_copy_memcpy_from_child",
|
"calldata_copy_memcpy_from_child",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,12 +9,52 @@ use crate::eravm::Dependency;
|
|||||||
/// Translates the `sha3` instruction.
|
/// Translates the `sha3` instruction.
|
||||||
///
|
///
|
||||||
pub fn sha3<'ctx, D>(
|
pub fn sha3<'ctx, D>(
|
||||||
_context: &mut Context<'ctx, D>,
|
context: &mut Context<'ctx, D>,
|
||||||
_offset: inkwell::values::IntValue<'ctx>,
|
offset: inkwell::values::IntValue<'ctx>,
|
||||||
_length: inkwell::values::IntValue<'ctx>,
|
length: inkwell::values::IntValue<'ctx>,
|
||||||
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
|
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
|
||||||
where
|
where
|
||||||
D: Dependency + Clone,
|
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")?))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ void * memset(void *b, int c, size_t len) {
|
|||||||
return b;
|
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
|
// Exports
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user