forked from polkadot-evm/frontier
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
precompiles utils + crowdloan precompile refactor (polkadot-evm#642)
* refactor data utils * improve usage of EvmDataWriter * refactor crowdloan precompile to use utils * make data util more generic to prepare for array support * parse arrays + address type + some tests * array tests + fix bug with arrays * docs + logs costs + record cost early * safer cursor + fix log cost * better conversion from H160 to AccountId * test bool + fmt * fix bug in record_cost
- Loading branch information
Showing
3 changed files
with
767 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,306 @@ | ||
// Copyright 2019-2021 PureStake Inc. | ||
// This file is part of Moonbeam. | ||
|
||
// Moonbeam 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. | ||
|
||
// Moonbeam 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 Moonbeam. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
use super::{error, EvmResult}; | ||
use core::ops::Range; | ||
use sp_core::{H160, H256, U256}; | ||
use sp_std::{convert::TryInto, vec, vec::Vec}; | ||
|
||
/// The `address` type of Solidity. | ||
/// H160 could represent 2 types of data (bytes20 and address) that are not encoded the same way. | ||
/// To avoid issues writing H160 is thus not supported. | ||
#[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||
pub struct Address(pub H160); | ||
|
||
impl From<H160> for Address { | ||
fn from(a: H160) -> Address { | ||
Address(a) | ||
} | ||
} | ||
|
||
impl From<Address> for H160 { | ||
fn from(a: Address) -> H160 { | ||
a.0 | ||
} | ||
} | ||
|
||
/// Wrapper around an EVM input slice, helping to parse it. | ||
/// Provide functions to parse common types. | ||
#[derive(Clone, Copy, Debug)] | ||
pub struct EvmDataReader<'a> { | ||
input: &'a [u8], | ||
cursor: usize, | ||
} | ||
|
||
impl<'a> EvmDataReader<'a> { | ||
/// Create a new input parser. | ||
pub fn new(input: &'a [u8]) -> Self { | ||
Self { input, cursor: 0 } | ||
} | ||
|
||
/// Check the input has at least the correct amount of arguments before the end (32 bytes values). | ||
pub fn expect_arguments(&self, args: usize) -> EvmResult { | ||
if self.input.len() >= self.cursor + args * 32 { | ||
Ok(()) | ||
} else { | ||
Err(error("input doesn't match expected length")) | ||
} | ||
} | ||
|
||
/// Read data from the input. | ||
pub fn read<T: EvmData>(&mut self) -> EvmResult<T> { | ||
T::read(self) | ||
} | ||
|
||
/// Read raw bytes from the input. | ||
/// Doesn't handle any alignement checks, prefer using `read` instead of possible. | ||
/// Returns an error if trying to parse out of bounds. | ||
pub fn read_raw_bytes(&mut self, len: usize) -> EvmResult<&[u8]> { | ||
let range = self.move_cursor(len)?; | ||
|
||
let data = self | ||
.input | ||
.get(range) | ||
.ok_or_else(|| error("tried to parse raw bytes out of bounds"))?; | ||
|
||
Ok(data) | ||
} | ||
|
||
/// Parse (4 bytes) selector. | ||
/// Returns an error if trying to parse out of bounds. | ||
pub fn read_selector(&mut self) -> EvmResult<&[u8]> { | ||
self.read_raw_bytes(4) | ||
.map_err(|_| error("tried to parse selector out of bounds")) | ||
} | ||
|
||
/// 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>> { | ||
let start = self.cursor; | ||
let end = self | ||
.cursor | ||
.checked_add(len) | ||
.ok_or_else(|| error("data reading cursor overflow"))?; | ||
|
||
self.cursor = end; | ||
|
||
Ok(start..end) | ||
} | ||
} | ||
|
||
/// Help build an EVM input/output data. | ||
#[derive(Clone, Debug)] | ||
pub struct EvmDataWriter { | ||
data: Vec<u8>, | ||
arrays: Vec<Array>, | ||
} | ||
|
||
#[derive(Clone, Debug)] | ||
struct Array { | ||
offset_position: usize, | ||
data: Vec<u8>, | ||
inner_arrays: Vec<Array>, | ||
} | ||
|
||
impl EvmDataWriter { | ||
/// Creates a new empty output builder. | ||
pub fn new() -> Self { | ||
Self { | ||
data: vec![], | ||
arrays: vec![], | ||
} | ||
} | ||
|
||
/// Return the built data. | ||
pub fn build(mut self) -> Vec<u8> { | ||
Self::build_arrays(&mut self.data, self.arrays, 0); | ||
|
||
self.data | ||
} | ||
|
||
/// Build the array into data. | ||
/// `global_offset` represents the start of the frame we are modifying. | ||
/// While the main data will have a `global_offset` of 0, inner arrays will have a | ||
/// `global_offset` corresponding to the start its parent array size data. | ||
fn build_arrays(output: &mut Vec<u8>, arrays: Vec<Array>, global_offset: usize) { | ||
for mut array in arrays { | ||
let offset_position = array.offset_position; | ||
let offset_position_end = offset_position + 32; | ||
let free_space_offset = output.len() + global_offset; | ||
|
||
// Override dummy offset to the offset it will be in the final output. | ||
U256::from(free_space_offset) | ||
.to_big_endian(&mut output[offset_position..offset_position_end]); | ||
|
||
// Build inner arrays if any. | ||
Self::build_arrays(&mut array.data, array.inner_arrays, free_space_offset); | ||
|
||
// Append this data at the end of the current output. | ||
output.append(&mut array.data); | ||
} | ||
} | ||
|
||
/// Write arbitrary bytes. | ||
/// Doesn't handle any alignement checks, prefer using `write` instead of possible. | ||
pub fn write_raw_bytes(mut self, value: &[u8]) -> Self { | ||
self.data.extend_from_slice(value); | ||
self | ||
} | ||
|
||
/// Write data of requested type. | ||
pub fn write<T: EvmData>(mut self, value: T) -> Self { | ||
T::write(&mut self, value); | ||
self | ||
} | ||
} | ||
|
||
impl Default for EvmDataWriter { | ||
fn default() -> Self { | ||
Self::new() | ||
} | ||
} | ||
|
||
/// Data that can be converted from and to EVM data types. | ||
pub trait EvmData: Sized { | ||
fn read(reader: &mut EvmDataReader) -> EvmResult<Self>; | ||
fn write(writer: &mut EvmDataWriter, value: Self); | ||
} | ||
|
||
impl EvmData for H256 { | ||
fn read(reader: &mut EvmDataReader) -> EvmResult<Self> { | ||
let range = reader.move_cursor(32)?; | ||
|
||
let data = reader | ||
.input | ||
.get(range) | ||
.ok_or_else(|| error("tried to parse H256 out of bounds"))?; | ||
|
||
Ok(H256::from_slice(data)) | ||
} | ||
|
||
fn write(writer: &mut EvmDataWriter, value: Self) { | ||
writer.data.extend_from_slice(&value.as_bytes()); | ||
} | ||
} | ||
|
||
impl EvmData for Address { | ||
fn read(reader: &mut EvmDataReader) -> EvmResult<Self> { | ||
let range = reader.move_cursor(32)?; | ||
|
||
let data = reader | ||
.input | ||
.get(range) | ||
.ok_or_else(|| error("tried to parse H160 out of bounds"))?; | ||
|
||
Ok(H160::from_slice(&data[12..32]).into()) | ||
} | ||
|
||
fn write(writer: &mut EvmDataWriter, value: Self) { | ||
H256::write(writer, value.0.into()); | ||
} | ||
} | ||
|
||
impl EvmData for U256 { | ||
fn read(reader: &mut EvmDataReader) -> EvmResult<Self> { | ||
let range = reader.move_cursor(32)?; | ||
|
||
let data = reader | ||
.input | ||
.get(range) | ||
.ok_or_else(|| error("tried to parse U256 out of bounds"))?; | ||
|
||
Ok(U256::from_big_endian(data)) | ||
} | ||
|
||
fn write(writer: &mut EvmDataWriter, value: Self) { | ||
let mut buffer = [0u8; 32]; | ||
value.to_big_endian(&mut buffer); | ||
writer.data.extend_from_slice(&buffer); | ||
} | ||
} | ||
|
||
impl EvmData for bool { | ||
fn read(reader: &mut EvmDataReader) -> EvmResult<Self> { | ||
let h256 = H256::read(reader).map_err(|_| error("tried to parse bool out of bounds"))?; | ||
|
||
Ok(!h256.is_zero()) | ||
} | ||
|
||
fn write(writer: &mut EvmDataWriter, value: Self) { | ||
let mut buffer = [0u8; 32]; | ||
if value { | ||
buffer[31] = 1; | ||
} | ||
|
||
writer.data.extend_from_slice(&buffer); | ||
} | ||
} | ||
|
||
impl<T: EvmData> EvmData for Vec<T> { | ||
fn read(reader: &mut EvmDataReader) -> EvmResult<Self> { | ||
let array_start: usize = reader | ||
.read::<U256>() | ||
.map_err(|_| error("tried to parse array offset out of bounds"))? | ||
.try_into() | ||
.map_err(|_| error("array offset is too large"))?; | ||
|
||
// We temporarily move the cursor to the offset, we'll set it back afterward. | ||
let original_cursor = reader.cursor; | ||
reader.cursor = array_start; | ||
|
||
let array_size: usize = reader | ||
.read::<U256>() | ||
.map_err(|_| error("tried to parse array length out of bounds"))? | ||
.try_into() | ||
.map_err(|_| error("array length is too large"))?; | ||
|
||
let mut array = vec![]; | ||
|
||
for _ in 0..array_size { | ||
array.push(reader.read()?); | ||
} | ||
|
||
// We set back the cursor to its original location. | ||
reader.cursor = original_cursor; | ||
|
||
Ok(array) | ||
} | ||
|
||
fn write(writer: &mut EvmDataWriter, value: Self) { | ||
let offset_position = writer.data.len(); | ||
H256::write(writer, H256::repeat_byte(0xff)); | ||
// 0xff = When debugging it makes spoting offset values easier. | ||
|
||
let mut inner_writer = EvmDataWriter::new(); | ||
|
||
// Write length. | ||
inner_writer = inner_writer.write(U256::from(value.len())); | ||
|
||
// Write elements of array. | ||
for inner in value { | ||
inner_writer = inner_writer.write(inner); | ||
} | ||
|
||
let array = Array { | ||
offset_position, | ||
data: inner_writer.data, | ||
inner_arrays: inner_writer.arrays, | ||
}; | ||
|
||
writer.arrays.push(array); | ||
} | ||
} |
Oops, something went wrong.