diff --git a/Cargo.lock b/Cargo.lock index fccfb0b1a1..177000ebce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6103,6 +6103,8 @@ dependencies = [ "once_cell", "parking_lot 0.12.1", "petgraph 0.5.1", + "primitive-types 0.12.1", + "rlp", "serde 1.0.195", "serde_bytes", "serde_json", @@ -9043,7 +9045,6 @@ dependencies = [ "move-core-types", "move-resource-viewer", "moveos-types", - "open-fastrlp", "rand 0.8.5", "rooch-config", "rooch-open-rpc", diff --git a/Cargo.toml b/Cargo.toml index 3c4f43b859..871fbfa8e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -230,7 +230,7 @@ argon2 = "0.5.2" rpassword = "7.2.0" fixed-hash = "0.8.0" uint = "0.9.5" -open-fastrlp = "0.1.4" +rlp = "0.5.2" const-hex = "1.6.2" cached = "0.43.0" diesel = { version = "2.1.0", features = [ diff --git a/crates/rooch-rpc-api/Cargo.toml b/crates/rooch-rpc-api/Cargo.toml index ba04043f60..194ff5776c 100644 --- a/crates/rooch-rpc-api/Cargo.toml +++ b/crates/rooch-rpc-api/Cargo.toml @@ -38,7 +38,6 @@ bigdecimal = { workspace = true } fixed-hash = { workspace = true } uint = { workspace = true } bytes = { workspace = true } -open-fastrlp = { workspace = true } const-hex = { workspace = true } bitcoin = { workspace = true } bitcoincore-rpc = { workspace = true } diff --git a/moveos/moveos-stdlib/Cargo.toml b/moveos/moveos-stdlib/Cargo.toml index 44e31b53d3..8c9bfb90fc 100644 --- a/moveos/moveos-stdlib/Cargo.toml +++ b/moveos/moveos-stdlib/Cargo.toml @@ -30,6 +30,8 @@ petgraph = { workspace = true } parking_lot = { workspace = true } itertools = { workspace = true } ciborium = { workspace = true } +rlp = { workspace = true } +primitive-types = { workspace = true } move-binary-format = { workspace = true } move-bytecode-utils = { workspace = true } diff --git a/moveos/moveos-stdlib/moveos-stdlib/doc/rlp.md b/moveos/moveos-stdlib/moveos-stdlib/doc/rlp.md index 605fe70883..c224ab5edf 100644 --- a/moveos/moveos-stdlib/moveos-stdlib/doc/rlp.md +++ b/moveos/moveos-stdlib/moveos-stdlib/doc/rlp.md @@ -7,6 +7,7 @@ Utility for converting a Move value to its binary representation in RLP(Recursiv https://ethereum.org/nl/developers/docs/data-structures-and-encoding/rlp/ +- [Constants](#@Constants_0) - [Function `to_bytes`](#0x2_rlp_to_bytes) - [Function `from_bytes`](#0x2_rlp_from_bytes) @@ -15,6 +16,29 @@ https://ethereum.org/nl/developers/docs/data-structures-and-encoding/rlp/ + + +## Constants + + + + + + +
const ErrorRLPDeserializationFailure: u64 = 2;
+
+
+
+
+
+
+
+
+const ErrorRLPSerializationFailure: u64 = 1;
+
+
+
+
## Function `to_bytes`
@@ -32,5 +56,6 @@ https://ethereum.org/nl/developers/docs/data-structures-and-encoding/rlp/
-public(friend) fun from_bytes<MoveValue>(bytes: &vector<u8>): MoveValue
+#[data_struct(#[MoveValue])]
+public fun from_bytes<MoveValue>(bytes: vector<u8>): MoveValue
diff --git a/moveos/moveos-stdlib/moveos-stdlib/sources/rlp.move b/moveos/moveos-stdlib/moveos-stdlib/sources/rlp.move
index bb6a0e671b..393c82df5c 100644
--- a/moveos/moveos-stdlib/moveos-stdlib/sources/rlp.move
+++ b/moveos/moveos-stdlib/moveos-stdlib/sources/rlp.move
@@ -5,7 +5,12 @@
/// https://ethereum.org/nl/developers/docs/data-structures-and-encoding/rlp/
module moveos_std::rlp{
+ const ErrorRLPSerializationFailure: u64 = 1;
+ const ErrorRLPDeserializationFailure: u64 = 2;
+
native public fun to_bytes(value: &MoveValue): vector;
- public(friend) native fun from_bytes(bytes: &vector): MoveValue;
+
+ #[data_struct(MoveValue)]
+ public native fun from_bytes(bytes: vector): MoveValue;
}
\ No newline at end of file
diff --git a/moveos/moveos-stdlib/moveos-stdlib/tests/rlp_tests.move b/moveos/moveos-stdlib/moveos-stdlib/tests/rlp_tests.move
new file mode 100644
index 0000000000..4f674391a8
--- /dev/null
+++ b/moveos/moveos-stdlib/moveos-stdlib/tests/rlp_tests.move
@@ -0,0 +1,104 @@
+// Copyright (c) RoochNetwork
+// SPDX-License-Identifier: Apache-2.0
+
+// Copyright (c) Mysten Labs, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+#[test_only]
+module moveos_std::rlp_tests {
+ use std::debug;
+ use std::vector;
+ use std::string::String;
+ use moveos_std::rlp;
+
+ #[data_struct]
+ struct PrimaryStruct has copy, drop {
+ a: u8,
+ b: u16,
+ c: u32,
+ d: u64,
+ e: u128,
+ f: u256,
+ me: address,
+ }
+
+ #[data_struct]
+ struct ChildStruct has copy, drop {
+ c: PrimaryStruct,
+ }
+
+ #[data_struct]
+ struct NestedStruct has copy, drop {
+ child: ChildStruct,
+ v: vector,
+ }
+
+ #[test]
+ fun test_basic() {
+ let bytes = rlp::to_bytes(&0u8);
+ assert!(bytes == x"80", 1);
+
+ let bytes = rlp::to_bytes(&x"0400"); // 1024
+ assert!(bytes == x"820400", 2);
+
+ let bytes = rlp::to_bytes(&1024u64);
+ assert!(bytes == x"820400", 3);
+
+ let bytes = rlp::to_bytes(&b"dog");
+ assert!(bytes == x"83646f67", 4);
+
+ let bytes = rlp::to_bytes(&std::string::utf8(b"dog"));
+ assert!(bytes == x"c483646f67", 5);
+
+ let v = vector::empty();
+ vector::push_back(&mut v, 1);
+ vector::push_back(&mut v, 2);
+ let bytes = rlp::to_bytes(&v);
+ assert!(bytes == x"c20102", 6);
+
+ let bytes = rlp::to_bytes(&@0x42);
+ assert!(bytes == x"a00000000000000000000000000000000000000000000000000000000000000042", 7);
+ }
+
+ fun primary_struct_instantiation(offset: u8): PrimaryStruct {
+ let v = vector::empty();
+ vector::push_back(&mut v, 1);
+ vector::push_back(&mut v, 2);
+ PrimaryStruct {
+ a: 1 + offset,
+ b: 256,
+ c: 3 + (offset as u32),
+ d: 4 + (offset as u64),
+ e: 5 + (offset as u128),
+ f: 6 + (offset as u256),
+ me: @0x42
+ }
+ }
+
+ fun nested_struct_instantiation(): NestedStruct {
+ let v = vector::empty();
+ vector::push_back(&mut v, primary_struct_instantiation(1));
+ let child = ChildStruct { c: primary_struct_instantiation(2)};
+ let data = NestedStruct { child: child, v: v };
+ data
+ }
+
+ #[test]
+ fun test_primary_struct() {
+ let data = primary_struct_instantiation(1);
+ let bytes = rlp::to_bytes(&data);
+ debug::print(&bytes);
+ let decoded_data = rlp::from_bytes(bytes);
+ assert!(decoded_data == data, 1);
+ }
+
+ #[test]
+ fun test_nested_struct() {
+ let data = nested_struct_instantiation();
+ let bytes = rlp::to_bytes(&data);
+ debug::print(&bytes);
+ let decoded_data = rlp::from_bytes(bytes);
+ assert!(decoded_data.child == data.child, 1);
+ assert!(vector::borrow(&decoded_data.v, 0) == vector::borrow(&data.v, 0), 2);
+ }
+}
\ No newline at end of file
diff --git a/moveos/moveos-stdlib/src/natives/moveos_stdlib/rlp.rs b/moveos/moveos-stdlib/src/natives/moveos_stdlib/rlp.rs
index 71ce9df752..39ded4c9db 100644
--- a/moveos/moveos-stdlib/src/natives/moveos_stdlib/rlp.rs
+++ b/moveos/moveos-stdlib/src/natives/moveos_stdlib/rlp.rs
@@ -1,36 +1,205 @@
// Copyright (c) RoochNetwork
// SPDX-License-Identifier: Apache-2.0
-use move_binary_format::errors::PartialVMResult;
-use move_core_types::gas_algebra::InternalGas;
+use log::info;
+use move_binary_format::errors::{PartialVMError, PartialVMResult};
+use move_core_types::account_address::AccountAddress;
+use move_core_types::gas_algebra::{InternalGas, InternalGasPerByte, NumBytes};
+use move_core_types::u256::{self, U256_NUM_BYTES};
+use move_core_types::value::MoveTypeLayout;
+use move_core_types::value::MoveValue;
+use move_core_types::vm_status::StatusCode;
use move_vm_runtime::native_functions::{NativeContext, NativeFunction};
use move_vm_types::{
- loaded_data::runtime_types::Type, natives::function::NativeResult, values::Value,
+ loaded_data::runtime_types::Type,
+ natives::function::NativeResult,
+ pop_arg,
+ values::{values_impl::Reference, Struct, Value},
};
+use primitive_types::U256 as PrimitiveU256;
+use rlp::{self, Rlp, RlpStream};
+use smallvec::smallvec;
use std::{collections::VecDeque, sync::Arc};
use crate::natives::helpers::make_module_natives;
+const E_RLP_SERIALIZATION_FAILURE: u64 = 1;
+const E_RLP_DESERIALIZATION_FAILURE: u64 = 2;
+
+struct MoveValueWrapper {
+ layout: MoveTypeLayout,
+ val: MoveValue,
+}
+
+impl rlp::Encodable for MoveValueWrapper {
+ fn rlp_append(&self, s: &mut RlpStream) {
+ use MoveTypeLayout as L;
+ match (&self.layout, &self.val) {
+ (L::Struct(layout), MoveValue::Struct(struct_)) => {
+ let layout_fields = layout.fields();
+ let value_fields = struct_.fields();
+ s.begin_list(layout_fields.len());
+ for (layout, value) in layout_fields.iter().zip(value_fields) {
+ s.append(&MoveValueWrapper {
+ layout: layout.clone(),
+ val: value.clone(),
+ });
+ }
+ }
+ (L::Bool, MoveValue::Bool(b)) => b.rlp_append(s),
+ (L::U8, MoveValue::U8(i)) => i.rlp_append(s),
+ (L::U16, MoveValue::U16(i)) => i.rlp_append(s),
+ (L::U32, MoveValue::U32(i)) => i.rlp_append(s),
+ (L::U64, MoveValue::U64(i)) => i.rlp_append(s),
+ (L::U128, MoveValue::U128(i)) => i.rlp_append(s),
+ (L::U256, MoveValue::U256(i)) => {
+ let slice = i.to_le_bytes();
+ let value = PrimitiveU256::from_little_endian(&slice);
+ let leading_empty_bytes = value.leading_zeros() as usize / 8;
+ let mut buffer = [0u8; U256_NUM_BYTES];
+ value.to_big_endian(&mut buffer);
+ s.encoder().encode_value(&buffer[leading_empty_bytes..]);
+ }
+ (L::Address, MoveValue::Address(a)) => a.to_vec().rlp_append(s),
+ (L::Signer, MoveValue::Signer(a)) => a.to_vec().rlp_append(s),
+ (L::Vector(layout), MoveValue::Vector(v)) => {
+ let layout = &**layout;
+ match layout {
+ L::U8 => {
+ let mut bytes = Vec::with_capacity(v.len());
+
+ for byte in v {
+ match byte {
+ MoveValue::U8(u8) => {
+ bytes.push(*u8);
+ }
+ _ => unreachable!("This should not happen."),
+ }
+ }
+ bytes.rlp_append(s);
+ }
+ _ => {
+ s.begin_list(v.len());
+ for val in v {
+ s.append(&MoveValueWrapper {
+ layout: layout.clone(),
+ val: val.clone(),
+ });
+ }
+ }
+ }
+ }
+ _ => todo!(),
+ }
+ }
+}
+
+fn decode_rlp(rlp: Rlp, layout: MoveTypeLayout) -> anyhow::Result {
+ let value = match layout {
+ MoveTypeLayout::Bool => Value::bool(rlp.as_val::()?),
+ MoveTypeLayout::U8 => Value::u8(rlp.as_val::()?),
+ MoveTypeLayout::U16 => Value::u16(rlp.as_val::()?),
+ MoveTypeLayout::U32 => Value::u32(rlp.as_val::()?),
+ MoveTypeLayout::U64 => Value::u64(rlp.as_val::()?),
+ MoveTypeLayout::U128 => Value::u128(rlp.as_val::()?),
+ MoveTypeLayout::U256 => {
+ let bytes = rlp.as_val::>()?;
+ let value = PrimitiveU256::from_big_endian(&bytes);
+ let mut buffer = [0u8; U256_NUM_BYTES];
+ value.to_little_endian(&mut buffer);
+ Value::u256(u256::U256::from_le_bytes(&buffer))
+ }
+ MoveTypeLayout::Address => {
+ let bytes = rlp.as_val::>()?;
+ AccountAddress::try_from(bytes).map(Value::address)?
+ }
+ MoveTypeLayout::Signer => {
+ let bytes = rlp.as_val::>()?;
+ AccountAddress::try_from(bytes).map(Value::signer)?
+ }
+ MoveTypeLayout::Struct(ty) => {
+ let mut fields = vec![];
+ for (index, field_ty) in ty.into_fields().into_iter().enumerate() {
+ let val = decode_rlp(rlp.at(index)?, field_ty)?;
+ fields.push(val);
+ }
+ Value::struct_(Struct::pack(fields))
+ }
+ MoveTypeLayout::Vector(layout) => {
+ match *layout.clone() {
+ MoveTypeLayout::U8 => {
+ let bytes = rlp.as_val::>()?;
+ Value::vector_u8(bytes)
+ }
+ _ => {
+ let count = rlp.item_count()?;
+
+ let mut elements = vec![];
+ for i in 0..count {
+ let val = decode_rlp(rlp.at(i)?, *layout.clone())?;
+ elements.push(val);
+ }
+
+ // TODO: This API may break
+ Value::vector_for_testing_only(elements)
+ }
+ }
+ }
+ };
+ Ok(value)
+}
+
#[derive(Debug, Clone)]
pub struct ToBytesGasParameters {
pub base: InternalGas,
+ pub per_byte: InternalGasPerByte,
}
impl ToBytesGasParameters {
pub fn zeros() -> Self {
- Self { base: 0.into() }
+ Self {
+ base: 0.into(),
+ per_byte: 0.into(),
+ }
}
}
/// Rust implementation of Move's `native public fun to_bytes(&T): vector in rlp module`
#[inline]
fn native_to_bytes(
- _gas_params: &ToBytesGasParameters,
- _context: &mut NativeContext,
- mut _ty_args: Vec,
- mut _args: VecDeque,
+ gas_params: &ToBytesGasParameters,
+ context: &mut NativeContext,
+ mut ty_args: Vec,
+ mut args: VecDeque,
) -> PartialVMResult {
- todo!()
+ debug_assert!(ty_args.len() == 1);
+ debug_assert!(args.len() == 1);
+
+ let mut cost = gas_params.base;
+
+ // pop type and value
+ let ref_to_val = pop_arg!(args, Reference);
+ let arg_type = ty_args.pop().unwrap();
+
+ // get type layout
+ let layout = match context.type_to_type_layout(&arg_type)? {
+ Some(layout) => layout,
+ None => {
+ return Ok(NativeResult::err(cost, E_RLP_SERIALIZATION_FAILURE));
+ }
+ };
+ // serialize value
+ let val = ref_to_val.read_ref()?.as_move_value(&layout);
+ let serialized_value = rlp::encode(&MoveValueWrapper { layout, val })
+ .into_iter()
+ .collect::>();
+
+ cost += gas_params.per_byte * NumBytes::new(serialized_value.len() as u64);
+
+ Ok(NativeResult::ok(
+ cost,
+ smallvec![Value::vector_u8(serialized_value)],
+ ))
}
pub fn make_native_to_bytes(gas_params: ToBytesGasParameters) -> NativeFunction {
Arc::new(
@@ -43,23 +212,53 @@ pub fn make_native_to_bytes(gas_params: ToBytesGasParameters) -> NativeFunction
#[derive(Debug, Clone)]
pub struct FromBytesGasParameters {
pub base: InternalGas,
+ pub per_byte: InternalGasPerByte,
}
impl FromBytesGasParameters {
pub fn zeros() -> Self {
- Self { base: 0.into() }
+ Self {
+ base: 0.into(),
+ per_byte: 0.into(),
+ }
}
}
/// Rust implementation of Move's `native public(friend) fun from_bytes(vector): T in rlp module`
#[inline]
fn native_from_bytes(
- _gas_params: &FromBytesGasParameters,
- _context: &mut NativeContext,
- mut _ty_args: Vec,
- mut _args: VecDeque,
+ gas_params: &FromBytesGasParameters,
+ context: &mut NativeContext,
+ ty_args: Vec,
+ mut args: VecDeque,
) -> PartialVMResult {
- todo!()
+ debug_assert_eq!(ty_args.len(), 1);
+ debug_assert_eq!(args.len(), 1);
+
+ let mut cost = gas_params.base;
+
+ // TODO(Gas): charge for getting the layout
+ let layout = context.type_to_type_layout(&ty_args[0])?.ok_or_else(|| {
+ PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(format!(
+ "Failed to get layout of type {:?} -- this should not happen",
+ ty_args[0]
+ ))
+ })?;
+
+ let bytes = pop_arg!(args, Vec);
+ cost += gas_params.per_byte * NumBytes::new(bytes.len() as u64);
+
+ // let value = Value::vector_u8(bytes.clone());
+ let rlp = Rlp::new(&bytes);
+
+ let value = match decode_rlp(rlp, layout) {
+ Ok(val) => val,
+ Err(err) => {
+ info!("RLP deserialization error: {:?}", err);
+ return Ok(NativeResult::err(cost, E_RLP_DESERIALIZATION_FAILURE));
+ }
+ };
+ Ok(NativeResult::ok(cost, smallvec![value]))
}
pub fn make_native_from_bytes(gas_params: FromBytesGasParameters) -> NativeFunction {