Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/xcm precompile update #996

Merged
merged 20 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

223 changes: 223 additions & 0 deletions precompiles/utils/src/bytes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
// This file is part of Astar.

// Copyright 2019-2022 PureStake Inc.
// Copyright (C) 2022-2023 Stake Technologies Pte.Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This file is part of Utils package, originally developed by Purestake Inc.
// Utils package used in Astar Network in terms of GPLv3.
//
// Utils is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Utils is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Utils. If not, see <http://www.gnu.org/licenses/>.

use super::*;
use alloc::borrow::ToOwned;
pub use alloc::string::String;
use sp_core::{ConstU32, Get};

type ConstU32Max = ConstU32<{ u32::MAX }>;

pub type UnboundedBytes = BoundedBytesString<BytesKind, ConstU32Max>;
pub type BoundedBytes<S> = BoundedBytesString<BytesKind, S>;

pub type UnboundedString = BoundedBytesString<StringKind, ConstU32Max>;
pub type BoundedString<S> = BoundedBytesString<StringKind, S>;

trait Kind {
fn signature() -> String;
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BytesKind;

impl Kind for BytesKind {
fn signature() -> String {
String::from("bytes")
}
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StringKind;

impl Kind for StringKind {
fn signature() -> String {
String::from("string")
}
}

/// The `bytes/string` type of Solidity.
/// It is different from `Vec<u8>` which will be serialized with padding for each `u8` element
/// of the array, while `Bytes` is tightly packed.
#[derive(Debug)]
pub struct BoundedBytesString<K, S> {
data: Vec<u8>,
_phantom: PhantomData<(K, S)>,
}

impl<K: Kind, S: Get<u32>> Clone for BoundedBytesString<K, S> {
fn clone(&self) -> Self {
Self {
data: self.data.clone(),
_phantom: PhantomData,
}
}
}

impl<K1, S1, K2, S2> PartialEq<BoundedBytesString<K2, S2>> for BoundedBytesString<K1, S1> {
fn eq(&self, other: &BoundedBytesString<K2, S2>) -> bool {
self.data.eq(&other.data)
}
}

impl<K, S> Eq for BoundedBytesString<K, S> {}

impl<K, S: Get<u32>> BoundedBytesString<K, S> {
pub fn as_bytes(&self) -> &[u8] {
&self.data
}

pub fn as_str(&self) -> Result<&str, sp_std::str::Utf8Error> {
sp_std::str::from_utf8(&self.data)
}
}

impl<K: Kind, S: Get<u32>> EvmData for BoundedBytesString<K, S> {
fn read(reader: &mut EvmDataReader) -> EvmResult<Self> {
let mut inner_reader = reader.read_pointer()?;

// Read bytes/string size.
let array_size: usize = inner_reader
.read::<U256>()
.map_err(|_| revert("length, out of bounds"))?
.try_into()
.map_err(|_| revert("length, value too large"))?;

if array_size > S::get() as usize {
return Err(revert("length, value too large").into());
}

// Get valid range over the bytes data.
let range = inner_reader.move_cursor(array_size)?;

let data = inner_reader
.get_input_from_range(range)
.ok_or_else(|| revert(K::signature()))?;

let bytes = Self {
data: data.to_owned(),
_phantom: PhantomData,
};

Ok(bytes)
}

fn write(writer: &mut EvmDataWriter, value: Self) {
let value: Vec<_> = value.into();
let length = value.len();

// Pad the data.
// Leave it as is if a multiple of 32, otherwise pad to next
// multiple or 32.
let chunks = length / 32;
let padded_size = match length % 32 {
0 => chunks * 32,
_ => (chunks + 1) * 32,
};

let mut value = value.to_vec();
value.resize(padded_size, 0);

writer.write_pointer(
EvmDataWriter::new()
.write(U256::from(length))
.write_raw_bytes(&value)
gitofdeepanshu marked this conversation as resolved.
Show resolved Hide resolved
.build(),
);
}

fn has_static_size() -> bool {
false
}
}

// BytesString <=> Vec/&[u8]

impl<K, S> From<BoundedBytesString<K, S>> for Vec<u8> {
fn from(value: BoundedBytesString<K, S>) -> Self {
value.data
}
}

impl<K, S> From<Vec<u8>> for BoundedBytesString<K, S> {
fn from(value: Vec<u8>) -> Self {
Self {
data: value,
_phantom: PhantomData,
}
}
}

impl<K, S> From<&[u8]> for BoundedBytesString<K, S> {
fn from(value: &[u8]) -> Self {
Self {
data: value.to_vec(),
_phantom: PhantomData,
}
}
}

impl<K, S, const N: usize> From<[u8; N]> for BoundedBytesString<K, S> {
fn from(value: [u8; N]) -> Self {
Self {
data: value.to_vec(),
_phantom: PhantomData,
}
}
}

impl<K, S, const N: usize> From<&[u8; N]> for BoundedBytesString<K, S> {
fn from(value: &[u8; N]) -> Self {
Self {
data: value.to_vec(),
_phantom: PhantomData,
}
}
}

// BytesString <=> String/str

impl<K, S> TryFrom<BoundedBytesString<K, S>> for String {
type Error = alloc::string::FromUtf8Error;

fn try_from(value: BoundedBytesString<K, S>) -> Result<Self, Self::Error> {
alloc::string::String::from_utf8(value.data)
}
}

impl<K, S> From<&str> for BoundedBytesString<K, S> {
fn from(value: &str) -> Self {
Self {
data: value.as_bytes().into(),
_phantom: PhantomData,
}
}
}

impl<K, S> From<String> for BoundedBytesString<K, S> {
fn from(value: String) -> Self {
Self {
data: value.as_bytes().into(),
_phantom: PhantomData,
}
}
}
115 changes: 111 additions & 4 deletions precompiles/utils/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
use crate::{revert, EvmResult};

use alloc::borrow::ToOwned;
use core::{any::type_name, ops::Range};
use core::{any::type_name, marker::PhantomData, ops::Range};
use impl_trait_for_tuples::impl_for_tuples;
use sp_core::{H160, H256, U256};
use sp_core::{Get, H160, H256, U256};
use sp_std::{convert::TryInto, vec, vec::Vec};

/// The `address` type of Solidity.
Expand Down Expand Up @@ -175,6 +175,11 @@ impl<'a> EvmDataReader<'a> {
})
}

/// Return Option<&[u8]> from a given range for EvmDataReader
pub fn get_input_from_range(&self, range: Range<usize>) -> Option<&[u8]> {
self.input.get(range)
}

/// Read remaining bytes
pub fn read_till_end(&mut self) -> EvmResult<&[u8]> {
let range = self.move_cursor(self.input.len() - self.cursor)?;
Expand All @@ -190,7 +195,7 @@ impl<'a> EvmDataReader<'a> {
/// Move the reading cursor with provided length, and return a range from the previous cursor
/// location to the new one.
/// Checks cursor overflows.
fn move_cursor(&mut self, len: usize) -> EvmResult<Range<usize>> {
pub fn move_cursor(&mut self, len: usize) -> EvmResult<Range<usize>> {
let start = self.cursor;
let end = self
.cursor
Expand Down Expand Up @@ -285,7 +290,7 @@ impl EvmDataWriter {

/// Write arbitrary bytes.
/// Doesn't handle any alignement checks, prefer using `write` instead if possible.
fn write_raw_bytes(mut self, value: &[u8]) -> Self {
pub fn write_raw_bytes(mut self, value: &[u8]) -> Self {
self.data.extend_from_slice(value);
self
}
Expand Down Expand Up @@ -604,3 +609,105 @@ impl EvmData for Bytes {
false
}
}

/// Wrapper around a Vec that provides a max length bound on read.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BoundedVec<T, S> {
inner: Vec<T>,
_phantom: PhantomData<S>,
}

impl<T: EvmData, S: Get<u32>> EvmData for BoundedVec<T, S> {
fn read(reader: &mut EvmDataReader) -> EvmResult<Self> {
let mut inner_reader = reader.read_pointer()?;

let array_size: usize = inner_reader
.read::<U256>()
.map_err(|_| revert("out of bounds: length of array"))?
.try_into()
.map_err(|_| revert("value too large : Array has more than max items allowed"))?;

if array_size > S::get() as usize {
return Err(revert("value too large : Array has more than max items allowed").into());
}

let mut array = vec![];

let mut item_reader = EvmDataReader {
input: inner_reader
.input
.get(32..)
.ok_or_else(|| revert("read out of bounds: array content"))?,
cursor: 0,
};

for _ in 0..array_size {
array.push(item_reader.read()?);
}

Ok(BoundedVec {
inner: array,
_phantom: PhantomData,
})
}

fn write(writer: &mut EvmDataWriter, value: Self) {
let value: Vec<_> = value.into();
let mut inner_writer = EvmDataWriter::new().write(U256::from(value.len()));

for inner in value {
// Any offset in items are relative to the start of the item instead of the
// start of the array. However if there is offseted data it must but appended after
// all items (offsets) are written. We thus need to rely on `compute_offsets` to do
// that, and must store a "shift" to correct the offsets.
let shift = inner_writer.data.len();
let item_writer = EvmDataWriter::new().write(inner);

inner_writer = inner_writer.write_raw_bytes(&item_writer.data);
for mut offset_datum in item_writer.offset_data {
offset_datum.offset_shift += 32;
offset_datum.offset_position += shift;
inner_writer.offset_data.push(offset_datum);
}
}

writer.write_pointer(inner_writer.build());
}

fn has_static_size() -> bool {
false
}
}

impl<T, S> From<Vec<T>> for BoundedVec<T, S> {
fn from(value: Vec<T>) -> Self {
BoundedVec {
inner: value,
_phantom: PhantomData,
}
}
}

impl<T: Clone, S> From<&[T]> for BoundedVec<T, S> {
fn from(value: &[T]) -> Self {
BoundedVec {
inner: value.to_vec(),
_phantom: PhantomData,
}
}
}

impl<T: Clone, S, const N: usize> From<[T; N]> for BoundedVec<T, S> {
fn from(value: [T; N]) -> Self {
BoundedVec {
inner: value.to_vec(),
_phantom: PhantomData,
}
}
}

impl<T, S> From<BoundedVec<T, S>> for Vec<T> {
fn from(value: BoundedVec<T, S>) -> Self {
value.inner
}
}
Loading