difftreelog
Add `ecrecovery` and `sr25519` precompiles
in: master
18 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2274,7 +2274,7 @@
"hash256-std-hasher",
"rlp",
"rlp-derive",
- "sha3",
+ "sha3 0.10.6",
"triehash",
]
@@ -2292,7 +2292,7 @@
"rlp",
"scale-info",
"serde",
- "sha3",
+ "sha3 0.10.6",
"triehash",
]
@@ -2350,7 +2350,7 @@
"rlp",
"scale-info",
"serde",
- "sha3",
+ "sha3 0.10.6",
]
[[package]]
@@ -2380,7 +2380,7 @@
"hex",
"proc-macro2",
"quote",
- "sha3",
+ "sha3 0.10.6",
"syn",
]
@@ -2415,7 +2415,7 @@
"environmental",
"evm-core",
"primitive-types 0.12.1",
- "sha3",
+ "sha3 0.10.6",
]
[[package]]
@@ -4958,7 +4958,7 @@
"digest 0.10.6",
"multihash-derive",
"sha2 0.10.6",
- "sha3",
+ "sha3 0.10.6",
"unsigned-varint",
]
@@ -5280,6 +5280,7 @@
"cumulus-primitives-utility",
"derivative",
"evm-coder",
+ "fp-evm",
"fp-evm-mapping",
"fp-rpc",
"fp-self-contained",
@@ -5294,6 +5295,7 @@
"impl-trait-for-tuples",
"log",
"logtest",
+ "num_enum",
"orml-tokens",
"orml-traits",
"orml-vesting",
@@ -5309,6 +5311,7 @@
"pallet-evm-coder-substrate",
"pallet-evm-contract-helpers",
"pallet-evm-migration",
+ "pallet-evm-precompile-simple",
"pallet-evm-transaction-payment",
"pallet-foreign-assets",
"pallet-fungible",
@@ -5333,6 +5336,7 @@
"parachain-info",
"parity-scale-codec 3.2.1",
"polkadot-parachain",
+ "precompile-utils-macro",
"rmrk-rpc",
"scale-info",
"serde",
@@ -6005,6 +6009,16 @@
]
[[package]]
+name = "pallet-evm-precompile-simple"
+version = "2.0.0-dev"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.33#c37ced433a29baad45899d6ab364fe437f97570e"
+dependencies = [
+ "fp-evm",
+ "ripemd",
+ "sp-io",
+]
+
+[[package]]
name = "pallet-evm-transaction-payment"
version = "0.1.1"
dependencies = [
@@ -8595,6 +8609,17 @@
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
+name = "precompile-utils-macro"
+version = "0.1.0"
+dependencies = [
+ "num_enum",
+ "proc-macro2",
+ "quote",
+ "sha3 0.8.2",
+ "syn",
+]
+
+[[package]]
name = "predicates"
version = "2.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -8850,6 +8875,7 @@
"cumulus-primitives-utility",
"derivative",
"evm-coder",
+ "fp-evm",
"fp-evm-mapping",
"fp-rpc",
"fp-self-contained",
@@ -8864,6 +8890,7 @@
"impl-trait-for-tuples",
"log",
"logtest",
+ "num_enum",
"orml-tokens",
"orml-traits",
"orml-vesting",
@@ -8879,6 +8906,7 @@
"pallet-evm-coder-substrate",
"pallet-evm-contract-helpers",
"pallet-evm-migration",
+ "pallet-evm-precompile-simple",
"pallet-evm-transaction-payment",
"pallet-foreign-assets",
"pallet-fungible",
@@ -8901,6 +8929,7 @@
"parachain-info",
"parity-scale-codec 3.2.1",
"polkadot-parachain",
+ "precompile-utils-macro",
"rmrk-rpc",
"scale-info",
"serde",
@@ -9249,6 +9278,15 @@
]
[[package]]
+name = "ripemd"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f"
+dependencies = [
+ "digest 0.10.6",
+]
+
+[[package]]
name = "rlp"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -10961,6 +10999,19 @@
[[package]]
name = "sha3"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf"
+dependencies = [
+ "block-buffer 0.7.3",
+ "byte-tools",
+ "digest 0.8.1",
+ "keccak",
+ "opaque-debug 0.2.3",
+]
+
+[[package]]
+name = "sha3"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9"
@@ -11396,7 +11447,7 @@
"byteorder",
"digest 0.10.6",
"sha2 0.10.6",
- "sha3",
+ "sha3 0.10.6",
"sp-std",
"twox-hash",
]
@@ -13026,6 +13077,7 @@
"cumulus-primitives-utility",
"derivative",
"evm-coder",
+ "fp-evm",
"fp-evm-mapping",
"fp-rpc",
"fp-self-contained",
@@ -13040,6 +13092,7 @@
"impl-trait-for-tuples",
"log",
"logtest",
+ "num_enum",
"orml-tokens",
"orml-traits",
"orml-vesting",
@@ -13055,6 +13108,7 @@
"pallet-evm-coder-substrate",
"pallet-evm-contract-helpers",
"pallet-evm-migration",
+ "pallet-evm-precompile-simple",
"pallet-evm-transaction-payment",
"pallet-foreign-assets",
"pallet-fungible",
@@ -13077,6 +13131,7 @@
"parachain-info",
"parity-scale-codec 3.2.1",
"polkadot-parachain",
+ "precompile-utils-macro",
"rmrk-rpc",
"scale-info",
"serde",
pallets/refungible/src/erc_token.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc_token.rs
+++ b/pallets/refungible/src/erc_token.rs
@@ -19,11 +19,6 @@
//! Provides ERC-20 standart support implementation and EVM API for unique extensions for Refungible Pallet.
//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.
-extern crate alloc;
-
-#[cfg(not(feature = "std"))]
-use alloc::format;
-
use core::{
char::{REPLACEMENT_CHARACTER, decode_utf16},
convert::TryInto,
runtime/common/config/ethereum.rsdiffbeforeafterboth--- a/runtime/common/config/ethereum.rs
+++ b/runtime/common/config/ethereum.rs
@@ -7,8 +7,10 @@
use sp_runtime::{RuntimeAppPublic, Perbill};
use crate::{
runtime_common::{
- dispatch::CollectionDispatchT, ethereum::sponsoring::EvmSponsorshipHandler,
- config::sponsoring::DefaultSponsoringRateLimit, DealWithFees,
+ config::sponsoring::DefaultSponsoringRateLimit,
+ DealWithFees,
+ dispatch::CollectionDispatchT,
+ ethereum::{precompiles::UniquePrecompiles, sponsoring::EvmSponsorshipHandler},
},
Runtime, Aura, Balances, RuntimeEvent, ChainId,
};
@@ -34,6 +36,7 @@
const EVM_DISPATCH_RATIO: Perbill = Perbill::from_percent(50);
parameter_types! {
pub BlockGasLimit: U256 = U256::from((NORMAL_DISPATCH_RATIO * EVM_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT / WeightTimePerGas::get()).ref_time());
+ pub PrecompilesValue: UniquePrecompiles<Runtime> = UniquePrecompiles::<_>::new();
}
pub struct EthereumFindAuthor<F>(core::marker::PhantomData<F>);
@@ -62,8 +65,8 @@
type CallOrigin = EnsureAddressTruncated<Self>;
type WithdrawOrigin = EnsureAddressTruncated<Self>;
type AddressMapping = HashedAddressMapping<Self::Hashing>;
- type PrecompilesType = ();
- type PrecompilesValue = ();
+ type PrecompilesType = UniquePrecompiles<Self>;
+ type PrecompilesValue = PrecompilesValue;
type Currency = Balances;
type RuntimeEvent = RuntimeEvent;
type OnMethodCall = (
runtime/common/ethereum/mod.rsdiffbeforeafterboth--- a/runtime/common/ethereum/mod.rs
+++ b/runtime/common/ethereum/mod.rs
@@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+pub mod precompiles;
pub mod self_contained_call;
pub mod sponsoring;
pub mod transaction_converter;
runtime/common/ethereum/precompiles/mod.rsdiffbeforeafterboth--- /dev/null
+++ b/runtime/common/ethereum/precompiles/mod.rs
@@ -0,0 +1,44 @@
+use pallet_evm::{Precompile, PrecompileHandle, PrecompileResult, PrecompileSet};
+use sp_core::H160;
+use sp_std::marker::PhantomData;
+
+use pallet_evm_precompile_simple::{ECRecover};
+use sr25519::Sr25519Precompile;
+
+mod sr25519;
+mod utils;
+
+pub struct UniquePrecompiles<R>(PhantomData<R>);
+
+impl<R> UniquePrecompiles<R>
+where
+ R: pallet_evm::Config,
+{
+ pub fn new() -> Self {
+ Self(Default::default())
+ }
+ pub fn used_addresses() -> [H160; 2] {
+ [hash(1), hash(20482)]
+ }
+}
+impl<R> PrecompileSet for UniquePrecompiles<R>
+where
+ R: pallet_evm::Config,
+{
+ fn execute(&self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
+ match handle.code_address() {
+ a if a == hash(1) => Some(ECRecover::execute(handle)),
+ // Sr25519 0x5002
+ a if a == hash(20482) => Some(Sr25519Precompile::<R>::execute(handle)),
+ _ => None,
+ }
+ }
+
+ fn is_precompile(&self, address: H160) -> bool {
+ Self::used_addresses().contains(&address)
+ }
+}
+
+fn hash(a: u64) -> H160 {
+ H160::from_low_u64_be(a)
+}
runtime/common/ethereum/precompiles/sr25519.rsdiffbeforeafterboth--- /dev/null
+++ b/runtime/common/ethereum/precompiles/sr25519.rs
@@ -0,0 +1,87 @@
+use fp_evm::{Context, ExitSucceed, PrecompileHandle, PrecompileOutput};
+use pallet_evm::Precompile;
+use sp_core::{crypto::UncheckedFrom, sr25519, H256};
+use sp_std::marker::PhantomData;
+use sp_std::prelude::*;
+
+use super::utils::{Bytes, EvmDataReader, EvmDataWriter, EvmResult, FunctionModifier, Gasometer};
+
+#[precompile_utils_macro::generate_function_selector]
+#[derive(Debug, PartialEq)]
+pub enum Action {
+ Verify = "verify(bytes32,bytes,bytes)",
+}
+
+/// A precompile to wrap substrate sr25519 functions.
+pub struct Sr25519Precompile<Runtime>(PhantomData<Runtime>);
+
+impl<Runtime: pallet_evm::Config> Precompile for Sr25519Precompile<Runtime> {
+ fn execute(handle: &mut impl PrecompileHandle) -> EvmResult<PrecompileOutput> {
+ log::trace!(target: "sr25519-precompile", "In sr25519 precompile");
+
+ let gasometer = Gasometer::new();
+
+ let (mut input, selector) = EvmDataReader::new_with_selector(&gasometer, handle.input())?;
+ let input = &mut input;
+
+ gasometer.check_function_modifier(
+ handle.context(),
+ handle.is_static(),
+ FunctionModifier::View,
+ )?;
+
+ match selector {
+ // Dispatchables
+ Action::Verify => Self::verify(input, &gasometer, handle.context()),
+ }
+ }
+}
+
+impl<Runtime: pallet_evm::Config> Sr25519Precompile<Runtime> {
+ fn verify(
+ input: &mut EvmDataReader,
+ gasometer: &Gasometer,
+ _: &Context,
+ ) -> EvmResult<PrecompileOutput> {
+ // Bound check
+ input.expect_arguments(gasometer, 3)?;
+
+ // Parse arguments
+ let public: sr25519::Public =
+ sr25519::Public::unchecked_from(input.read::<H256>(gasometer)?).into();
+ let signature_bytes: Vec<u8> = input.read::<Bytes>(gasometer)?.into();
+ let message: Vec<u8> = input.read::<Bytes>(gasometer)?.into();
+
+ // Parse signature
+ let signature_opt = sr25519::Signature::from_slice(&signature_bytes[..]);
+
+ let signature = if let Some(sig) = signature_opt {
+ sig
+ } else {
+ // Return `false` if signature length is wrong
+ return Ok(PrecompileOutput {
+ exit_status: ExitSucceed::Returned,
+ output: EvmDataWriter::new().write(false).build(),
+ });
+ };
+
+ log::trace!(
+ target: "sr25519-precompile",
+ "Verify signature {:?} for public {:?} and message {:?}",
+ signature, public, message,
+ );
+
+ let is_confirmed = sp_io::crypto::sr25519_verify(&signature, &message[..], &public);
+
+ log::trace!(
+ target: "sr25519-precompile",
+ "Verified signature {:?} is {:?}",
+ signature, is_confirmed,
+ );
+
+ Ok(PrecompileOutput {
+ exit_status: ExitSucceed::Returned,
+ output: EvmDataWriter::new().write(is_confirmed).build(),
+ })
+ }
+}
runtime/common/ethereum/precompiles/utils/data.rsdiffbeforeafterboth--- /dev/null
+++ b/runtime/common/ethereum/precompiles/utils/data.rs
@@ -0,0 +1,486 @@
+// Copyright 2019-2022 PureStake Inc.
+// Copyright 2022 Stake Technologies
+// 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::{EvmResult, Gasometer};
+
+use sp_std::borrow::ToOwned;
+use core::{any::type_name, 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
+ }
+}
+
+/// 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(Clone, Debug, Eq, PartialEq)]
+pub struct Bytes(pub Vec<u8>);
+
+impl From<&[u8]> for Bytes {
+ fn from(a: &[u8]) -> Self {
+ Self(a.to_owned())
+ }
+}
+
+impl From<&str> for Bytes {
+ fn from(a: &str) -> Self {
+ a.as_bytes().into()
+ }
+}
+
+impl Into<Vec<u8>> for Bytes {
+ fn into(self: Self) -> Vec<u8> {
+ self.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 }
+ }
+
+ /// Create a new input parser from a selector-initial input.
+ pub fn new_with_selector<T>(gasometer: &Gasometer, input: &'a [u8]) -> EvmResult<(Self, T)>
+ where
+ T: num_enum::TryFromPrimitive<Primitive = u32>,
+ {
+ if input.len() < 4 {
+ return Err(gasometer.revert("tried to parse selector out of bounds"));
+ }
+
+ let mut buffer = [0u8; 4];
+ buffer.copy_from_slice(&input[0..4]);
+ let selector = T::try_from_primitive(u32::from_be_bytes(buffer)).map_err(|_| {
+ log::trace!(
+ target: "precompile-utils",
+ "Failed to match function selector for {}",
+ type_name::<T>()
+ );
+ gasometer.revert("unknown selector")
+ })?;
+
+ Ok((Self::new(&input[4..]), selector))
+ }
+
+ /// Check the input has at least the correct amount of arguments before the end (32 bytes values).
+ pub fn expect_arguments(&self, gasometer: &Gasometer, args: usize) -> EvmResult {
+ if self.input.len() >= self.cursor + args * 32 {
+ Ok(())
+ } else {
+ Err(gasometer.revert("input doesn't match expected length"))
+ }
+ }
+
+ /// Read data from the input.
+ /// Must be provided a gasometer to generate correct Revert errors.
+ /// TODO : Benchmark and add cost of parsing to gasometer ?
+ pub fn read<T: EvmData>(&mut self, gasometer: &Gasometer) -> EvmResult<T> {
+ T::read(self, gasometer)
+ }
+
+ /// Reads a pointer, returning a reader targetting the pointed location.
+ pub fn read_pointer(&mut self, gasometer: &Gasometer) -> EvmResult<Self> {
+ let offset: usize = self
+ .read::<U256>(gasometer)
+ .map_err(|_| gasometer.revert("tried to parse array offset out of bounds"))?
+ .try_into()
+ .map_err(|_| gasometer.revert("array offset is too large"))?;
+
+ if offset >= self.input.len() {
+ return Err(gasometer.revert("pointer points out of bounds"));
+ }
+
+ Ok(Self {
+ input: &self.input[offset..],
+ cursor: 0,
+ })
+ }
+
+ /// 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, gasometer: &Gasometer, len: usize) -> EvmResult<Range<usize>> {
+ let start = self.cursor;
+ let end = self
+ .cursor
+ .checked_add(len)
+ .ok_or_else(|| gasometer.revert("data reading cursor overflow"))?;
+
+ self.cursor = end;
+
+ Ok(start..end)
+ }
+}
+
+/// Help build an EVM input/output data.
+///
+/// Functions takes `self` to allow chaining all calls like
+/// `EvmDataWriter::new().write(...).write(...).build()`.
+/// While it could be more ergonomic to take &mut self, this would
+/// prevent to have a `build` function that don't clone the output.
+#[derive(Clone, Debug)]
+pub struct EvmDataWriter {
+ pub(crate) data: Vec<u8>,
+ offset_data: Vec<OffsetDatum>,
+ selector: Option<u32>,
+}
+
+#[derive(Clone, Debug)]
+struct OffsetDatum {
+ // Offset location in the container data.
+ offset_position: usize,
+ // Data pointed by the offset that must be inserted at the end of container data.
+ data: Vec<u8>,
+ // Inside of arrays, the offset is not from the start of array data (length), but from the start
+ // of the item. This shift allow to correct this.
+ offset_shift: usize,
+}
+
+impl EvmDataWriter {
+ /// Creates a new empty output builder (without selector).
+ pub fn new() -> Self {
+ Self {
+ data: vec![],
+ offset_data: vec![],
+ selector: None,
+ }
+ }
+
+ /// Return the built data.
+ pub fn build(mut self) -> Vec<u8> {
+ Self::bake_offsets(&mut self.data, self.offset_data);
+
+ if let Some(selector) = self.selector {
+ let mut output = selector.to_be_bytes().to_vec();
+ output.append(&mut self.data);
+ output
+ } else {
+ self.data
+ }
+ }
+
+ /// Add offseted data at the end of this writer's data, updating the offsets.
+ fn bake_offsets(output: &mut Vec<u8>, offsets: Vec<OffsetDatum>) {
+ for mut offset_datum in offsets {
+ let offset_position = offset_datum.offset_position;
+ let offset_position_end = offset_position + 32;
+
+ // The offset is the distance between the start of the data and the
+ // start of the pointed data (start of a struct, length of an array).
+ // Offsets in inner data are relative to the start of their respective "container".
+ // However in arrays the "container" is actually the item itself instead of the whole
+ // array, which is corrected by `offset_shift`.
+ let free_space_offset = output.len() - offset_datum.offset_shift;
+
+ // 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]);
+
+ // Append this data at the end of the current output.
+ output.append(&mut offset_datum.data);
+ }
+ }
+
+ /// Write arbitrary bytes.
+ /// Doesn't handle any alignement checks, prefer using `write` instead if possible.
+ 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
+ }
+
+ /// Writes a pointer to given data.
+ /// The data will be appended when calling `build`.
+ /// Initially write a dummy value as offset in this writer's data, which will be replaced by
+ /// the correct offset once the pointed data is appended.
+ ///
+ /// Takes `&mut self` since its goal is to be used inside `EvmData` impl and not in chains.
+ pub fn write_pointer(&mut self, data: Vec<u8>) {
+ let offset_position = self.data.len();
+ H256::write(self, H256::repeat_byte(0xff));
+
+ self.offset_data.push(OffsetDatum {
+ offset_position,
+ data,
+ offset_shift: 0,
+ });
+ }
+}
+
+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, gasometer: &Gasometer) -> EvmResult<Self>;
+ fn write(writer: &mut EvmDataWriter, value: Self);
+}
+
+impl EvmData for H256 {
+ fn read(reader: &mut EvmDataReader, gasometer: &Gasometer) -> EvmResult<Self> {
+ let range = reader.move_cursor(gasometer, 32)?;
+
+ let data = reader
+ .input
+ .get(range)
+ .ok_or_else(|| gasometer.revert("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, gasometer: &Gasometer) -> EvmResult<Self> {
+ let range = reader.move_cursor(gasometer, 32)?;
+
+ let data = reader
+ .input
+ .get(range)
+ .ok_or_else(|| gasometer.revert("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, gasometer: &Gasometer) -> EvmResult<Self> {
+ let range = reader.move_cursor(gasometer, 32)?;
+
+ let data = reader
+ .input
+ .get(range)
+ .ok_or_else(|| gasometer.revert("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);
+ }
+}
+
+macro_rules! impl_evmdata_for_uints {
+ ($($uint:ty, )*) => {
+ $(
+ impl EvmData for $uint {
+ fn read(reader: &mut EvmDataReader, gasometer: &Gasometer) -> EvmResult<Self> {
+ let range = reader.move_cursor(gasometer, 32)?;
+
+ let data = reader
+ .input
+ .get(range)
+ .ok_or_else(|| gasometer.revert(alloc::format!(
+ "tried to parse {} out of bounds", core::any::type_name::<Self>()
+ )))?;
+
+ let mut buffer = [0u8; core::mem::size_of::<Self>()];
+ buffer.copy_from_slice(&data[32 - core::mem::size_of::<Self>()..]);
+ Ok(Self::from_be_bytes(buffer))
+ }
+
+ fn write(writer: &mut EvmDataWriter, value: Self) {
+ let mut buffer = [0u8; 32];
+ buffer[32 - core::mem::size_of::<Self>()..].copy_from_slice(&value.to_be_bytes());
+ writer.data.extend_from_slice(&buffer);
+ }
+ }
+ )*
+ };
+}
+
+impl_evmdata_for_uints!(u16, u32, u64, u128,);
+
+// The implementation for u8 is specific, for performance reasons.
+impl EvmData for u8 {
+ fn read(reader: &mut EvmDataReader, gasometer: &Gasometer) -> EvmResult<Self> {
+ let range = reader.move_cursor(gasometer, 32)?;
+
+ let data = reader
+ .input
+ .get(range)
+ .ok_or_else(|| gasometer.revert("tried to parse u64 out of bounds"))?;
+
+ Ok(data[31])
+ }
+
+ fn write(writer: &mut EvmDataWriter, value: Self) {
+ let mut buffer = [0u8; 32];
+ buffer[31] = value;
+
+ writer.data.extend_from_slice(&buffer);
+ }
+}
+
+impl EvmData for bool {
+ fn read(reader: &mut EvmDataReader, gasometer: &Gasometer) -> EvmResult<Self> {
+ let h256 = H256::read(reader, gasometer)
+ .map_err(|_| gasometer.revert("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, gasometer: &Gasometer) -> EvmResult<Self> {
+ let mut inner_reader = reader.read_pointer(gasometer)?;
+
+ let array_size: usize = inner_reader
+ .read::<U256>(gasometer)
+ .map_err(|_| gasometer.revert("tried to parse array length out of bounds"))?
+ .try_into()
+ .map_err(|_| gasometer.revert("array length is too large"))?;
+
+ let mut array = vec![];
+
+ let mut item_reader = EvmDataReader {
+ input: inner_reader
+ .input
+ .get(32..)
+ .ok_or_else(|| gasometer.revert("try to read array items out of bound"))?,
+ cursor: 0,
+ };
+
+ for _ in 0..array_size {
+ array.push(item_reader.read(gasometer)?);
+ }
+
+ Ok(array)
+ }
+
+ fn write(writer: &mut EvmDataWriter, value: Self) {
+ 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());
+ }
+}
+
+impl EvmData for Bytes {
+ fn read(reader: &mut EvmDataReader, gasometer: &Gasometer) -> EvmResult<Self> {
+ let mut inner_reader = reader.read_pointer(gasometer)?;
+
+ // Read bytes/string size.
+ let array_size: usize = inner_reader
+ .read::<U256>(gasometer)
+ .map_err(|_| gasometer.revert("tried to parse bytes/string length out of bounds"))?
+ .try_into()
+ .map_err(|_| gasometer.revert("bytes/string length is too large"))?;
+
+ // Get valid range over the bytes data.
+ let range = inner_reader.move_cursor(gasometer, array_size)?;
+
+ let data = inner_reader
+ .input
+ .get(range)
+ .ok_or_else(|| gasometer.revert("tried to parse bytes/string out of bounds"))?;
+
+ let bytes = Self(data.to_owned());
+
+ Ok(bytes)
+ }
+
+ fn write(writer: &mut EvmDataWriter, value: Self) {
+ let length = value.0.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.0.to_vec();
+ value.resize(padded_size, 0);
+
+ writer.write_pointer(
+ EvmDataWriter::new()
+ .write(U256::from(length))
+ .write_raw_bytes(&value)
+ .build(),
+ );
+ }
+}
runtime/common/ethereum/precompiles/utils/macro/Cargo.tomldiffbeforeafterbothno changes
runtime/common/ethereum/precompiles/utils/macro/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/runtime/common/ethereum/precompiles/utils/macro/src/lib.rs
@@ -0,0 +1,115 @@
+// Copyright 2019-2022 PureStake Inc.
+// Copyright 2022 Stake Technologies
+// 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/>.
+
+#![crate_type = "proc-macro"]
+extern crate proc_macro;
+
+use proc_macro::TokenStream;
+use proc_macro2::Literal;
+use quote::{quote, quote_spanned};
+use sha3::{Digest, Keccak256};
+use std::convert::TryInto;
+use syn::{parse_macro_input, spanned::Spanned, Expr, ExprLit, Ident, ItemEnum, Lit};
+
+/// This macro allows to associate to each variant of an enumeration a discriminant (of type u32
+/// whose value corresponds to the first 4 bytes of the Hash Keccak256 of the character string
+///indicated by the user of this macro.
+///
+/// Usage:
+///
+/// ```ignore
+/// #[generate_function_selector]
+/// enum Action {
+/// Toto = "toto()",
+/// Tata = "tata()",
+/// }
+/// ```
+///
+/// Extanded to:
+///
+/// ```rust
+/// #[repr(u32)]
+/// enum Action {
+/// Toto = 119097542u32,
+/// Tata = 1414311903u32,
+/// }
+/// ```
+///
+#[proc_macro_attribute]
+pub fn generate_function_selector(_: TokenStream, input: TokenStream) -> TokenStream {
+ let item = parse_macro_input!(input as ItemEnum);
+
+ let ItemEnum {
+ attrs,
+ vis,
+ enum_token,
+ ident,
+ variants,
+ ..
+ } = item;
+
+ let mut ident_expressions: Vec<Ident> = vec![];
+ let mut variant_expressions: Vec<Expr> = vec![];
+ for variant in variants {
+ match variant.discriminant {
+ Some((_, Expr::Lit(ExprLit { lit, .. }))) => {
+ if let Lit::Str(lit_str) = lit {
+ let selector = u32::from_be_bytes(
+ Keccak256::digest(lit_str.value().as_ref())[..4]
+ .try_into()
+ .unwrap(),
+ );
+ ident_expressions.push(variant.ident);
+ variant_expressions.push(Expr::Lit(ExprLit {
+ lit: Lit::Verbatim(Literal::u32_suffixed(selector)),
+ attrs: Default::default(),
+ }));
+ } else {
+ return quote_spanned! {
+ lit.span() => compile_error("Expected literal string");
+ }
+ .into();
+ }
+ }
+ Some((_eg, expr)) => {
+ return quote_spanned! {
+ expr.span() => compile_error("Expected literal");
+ }
+ .into()
+ }
+ None => {
+ return quote_spanned! {
+ variant.span() => compile_error("Each variant must have a discriminant");
+ }
+ .into()
+ }
+ }
+ }
+
+ (quote! {
+ #(#attrs)*
+ #[derive(num_enum::TryFromPrimitive, num_enum::IntoPrimitive)]
+ #[repr(u32)]
+ #vis #enum_token #ident {
+ #(
+ #ident_expressions = #variant_expressions,
+ )*
+ }
+ })
+ .into()
+}
runtime/common/ethereum/precompiles/utils/mod.rsdiffbeforeafterboth--- /dev/null
+++ b/runtime/common/ethereum/precompiles/utils/mod.rs
@@ -0,0 +1,95 @@
+// Copyright 2019-2022 PureStake Inc.
+// Copyright 2022 Stake Technologies
+// 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 sp_std::borrow::ToOwned;
+use fp_evm::{Context, ExitRevert, PrecompileFailure};
+use sp_core::U256;
+use sp_std::marker::PhantomData;
+
+mod data;
+
+pub use data::{Bytes, EvmData, EvmDataReader, EvmDataWriter};
+
+/// Alias for Result returning an EVM precompile error.
+pub type EvmResult<T = ()> = Result<T, PrecompileFailure>;
+
+/// Helper functions requiring a Runtime.
+/// This runtime must of course implement `pallet_evm::Config`.
+#[derive(Clone, Copy, Debug)]
+pub struct RuntimeHelper<Runtime>(PhantomData<Runtime>);
+
+/// Represents modifiers a Solidity function can be annotated with.
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub enum FunctionModifier {
+ /// Function that doesn't modify the state.
+ View,
+ /// Function that modifies the state and accept funds.
+ Payable,
+}
+
+/// Custom Gasometer to record costs in precompiles.
+/// It is advised to record known costs as early as possible to
+/// avoid unecessary computations if there is an Out of Gas.
+///
+/// Provides functions related to reverts, as reverts takes the recorded amount
+/// of gas into account.
+#[derive(Clone, Copy, Debug)]
+pub struct Gasometer();
+
+impl Gasometer {
+ /// Create a new Gasometer with provided gas limit.
+ /// None is no limit.
+ pub fn new() -> Self {
+ Self()
+ }
+
+ /// Revert the execution, making the user pay for the the currently
+ /// recorded cost. It is better to **revert** instead of **error** as
+ /// erroring consumes the entire gas limit, and **revert** returns an error
+ /// message to the calling contract.
+ ///
+ /// TODO : Record cost of the input based on its size and handle Out of Gas ?
+ /// This might be required if we format revert messages using user data.
+ #[must_use]
+ pub fn revert(&self, output: impl AsRef<[u8]>) -> PrecompileFailure {
+ PrecompileFailure::Revert {
+ exit_status: ExitRevert::Reverted,
+ output: output.as_ref().to_owned(),
+ }
+ }
+
+ #[must_use]
+ /// Check that a function call is compatible with the context it is
+ /// called into.
+ pub fn check_function_modifier(
+ &self,
+ context: &Context,
+ is_static: bool,
+ modifier: FunctionModifier,
+ ) -> EvmResult {
+ if is_static && modifier != FunctionModifier::View {
+ return Err(self.revert("can't call non-static function in static context"));
+ }
+
+ if modifier != FunctionModifier::Payable && context.apparent_value > U256::zero() {
+ return Err(self.revert("function is not payable"));
+ }
+
+ Ok(())
+ }
+}
runtime/opal/Cargo.tomldiffbeforeafterboth--- a/runtime/opal/Cargo.toml
+++ b/runtime/opal/Cargo.toml
@@ -471,6 +471,7 @@
up-rpc = { path = "../../primitives/rpc", default-features = false }
app-promotion-rpc = { path = "../../primitives/app_promotion_rpc", default-features = false }
rmrk-rpc = { path = "../../primitives/rmrk-rpc", default-features = false }
+fp-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
pallet-inflation = { path = '../../pallets/inflation', default-features = false }
pallet-app-promotion = { path = '../../pallets/app-promotion', default-features = false }
@@ -494,10 +495,13 @@
pallet-base-fee = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
fp-self-contained = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
+pallet-evm-precompile-simple = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
evm-coder = { default-features = false, path = '../../crates/evm-coder' }
up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = 'polkadot-v0.9.33' }
pallet-foreign-assets = { default-features = false, path = "../../pallets/foreign-assets" }
pallet-maintenance = { default-features = false, path = "../../pallets/maintenance" }
+precompile-utils-macro = { path = "../common/ethereum/precompiles/utils/macro" }
+num_enum = { version = "0.5.3", default-features = false }
################################################################################
# Test dependencies
runtime/opal/src/lib.rsdiffbeforeafterboth--- a/runtime/opal/src/lib.rs
+++ b/runtime/opal/src/lib.rs
@@ -25,6 +25,8 @@
#[cfg(feature = "std")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
+extern crate alloc;
+
use frame_support::parameter_types;
use sp_version::RuntimeVersion;
runtime/quartz/Cargo.tomldiffbeforeafterboth--- a/runtime/quartz/Cargo.toml
+++ b/runtime/quartz/Cargo.toml
@@ -463,6 +463,7 @@
pallet-unique = { path = '../../pallets/unique', default-features = false }
up-rpc = { path = "../../primitives/rpc", default-features = false }
app-promotion-rpc = { path = "../../primitives/app_promotion_rpc", default-features = false }
+fp-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
pallet-inflation = { path = '../../pallets/inflation', default-features = false }
pallet-app-promotion = { path = '../../pallets/app-promotion', default-features = false }
@@ -486,10 +487,13 @@
pallet-base-fee = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
fp-self-contained = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
+pallet-evm-precompile-simple = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
evm-coder = { default-features = false, path = '../../crates/evm-coder' }
up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = 'polkadot-v0.9.33' }
pallet-foreign-assets = { default-features = false, path = "../../pallets/foreign-assets" }
pallet-maintenance = { default-features = false, path = "../../pallets/maintenance" }
+precompile-utils-macro = { path = "../common/ethereum/precompiles/utils/macro" }
+num_enum = { version = "0.5.3", default-features = false }
################################################################################
# Other Dependencies
runtime/quartz/src/lib.rsdiffbeforeafterboth--- a/runtime/quartz/src/lib.rs
+++ b/runtime/quartz/src/lib.rs
@@ -25,6 +25,8 @@
#[cfg(feature = "std")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
+extern crate alloc;
+
use frame_support::parameter_types;
use sp_version::RuntimeVersion;
runtime/unique/Cargo.tomldiffbeforeafterboth--- a/runtime/unique/Cargo.toml
+++ b/runtime/unique/Cargo.toml
@@ -479,11 +479,15 @@
pallet-base-fee = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
fp-self-contained = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
+fp-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
+pallet-evm-precompile-simple = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.33" }
evm-coder = { default-features = false, path = '../../crates/evm-coder' }
up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = 'polkadot-v0.9.33' }
pallet-foreign-assets = { default-features = false, path = "../../pallets/foreign-assets" }
pallet-maintenance = { default-features = false, path = "../../pallets/maintenance" }
+precompile-utils-macro = { path = "../common/ethereum/precompiles/utils/macro" }
+num_enum = { version = "0.5.3", default-features = false }
################################################################################
# Other Dependencies
runtime/unique/src/lib.rsdiffbeforeafterboth--- a/runtime/unique/src/lib.rs
+++ b/runtime/unique/src/lib.rs
@@ -25,6 +25,8 @@
#[cfg(feature = "std")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
+extern crate alloc;
+
use frame_support::parameter_types;
use sp_version::RuntimeVersion;
tests/src/eth/precompile.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/precompile.test.ts
@@ -0,0 +1,112 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network 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.
+
+// Unique Network 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 Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+
+import {expect, itEth, usingEthPlaygrounds} from './util';
+
+describe('Precompiles', () => {
+ let donor: IKeyringPair;
+
+ before(async function() {
+ await usingEthPlaygrounds(async (_, privateKey) => {
+ donor = await privateKey({filename: __filename});
+ });
+ });
+
+ itEth('ecrecover is supported', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const ecrecoverCompiledСontract = await helper.ethContract.compile(
+ 'ECRECOVER',
+ `
+ // SPDX-License-Identifier: MIT
+ pragma solidity ^0.8.17;
+
+ contract ECRECOVER{
+ address addressTest = 0x12Cb274aAD8251C875c0bf6872b67d9983E53fDd;
+ bytes32 msgHash1 = 0xc51dac836bc7841a01c4b631fa620904fc8724d7f9f1d3c420f0e02adf229d50;
+ bytes32 msgHash2 = 0xc51dac836bc7841a01c4b631fa620904fc8724d7f9f1d3c420f0e02adf229d51;
+ uint8 v = 0x1b;
+ bytes32 r = 0x44287513919034a471a7dc2b2ed121f95984ae23b20f9637ba8dff471b6719ef;
+ bytes32 s = 0x7d7dc30309a3baffbfd9342b97d0e804092c0aeb5821319aa732bc09146eafb4;
+
+
+ function verifyValid() public view returns(bool) {
+ // Use ECRECOVER to verify address
+ return ecrecover(msgHash1, v, r, s) == (addressTest);
+ }
+
+ function verifyInvalid() public view returns(bool) {
+ // Use ECRECOVER to verify address
+ return ecrecover(msgHash2, v, r, s) == (addressTest);
+ }
+ }
+ `,
+ );
+
+ const ecrecoverСontract = await helper.ethContract.deployByAbi(owner, ecrecoverCompiledСontract.abi, ecrecoverCompiledСontract.object);
+ expect(await ecrecoverСontract.methods.verifyValid().call({from: owner})).to.be.true;
+ expect(await ecrecoverСontract.methods.verifyInvalid().call({from: owner})).to.be.false;
+ });
+
+ itEth('sr25519 is supported', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const sr25519CompiledСontract = await helper.ethContract.compile(
+ 'SR25519Contract',
+ `
+ // SPDX-License-Identifier: MIT
+ pragma solidity ^0.8.17;
+
+ /**
+ * @title SR25519 signature interface.
+ */
+ interface SR25519 {
+ /**
+ * @dev Verify signed message using SR25519 crypto.
+ * @return A boolean confirming whether the public key is signer for the message.
+ */
+ function verify(
+ bytes32 public_key,
+ bytes calldata signature,
+ bytes calldata message
+ ) external view returns (bool);
+ }
+
+ contract SR25519Contract{
+ SR25519 public constant sr25519 = SR25519(0x0000000000000000000000000000000000005002);
+
+ bytes32 public_key = 0x96b2f9237ed0890fbeed891ebb81b91ac0d5d5b6e3afcdbc95df1b68d9f14036;
+ bytes signature = hex"4a5d733d7c568f2e88abf0467fd497f87c1be3e940ed897efdf9da72eaad394ef9918cb574ee99c54485775b17a0deaf46ff7a1f10346cea39fff0e4ede97689";
+ bytes message1 = hex"7372323535313920697320737570706f72746564";
+ bytes message2 = hex"7372323535313920697320737570706f7274656401";
+
+
+ function verifyValid() public view returns(bool) {
+ return sr25519.verify(public_key, signature, message1);
+ }
+
+ function verifyInvalid() public view returns(bool) {
+ return sr25519.verify(public_key, signature, message2);
+ }
+ }
+ `,
+ );
+
+ const sr25519Сontract = await helper.ethContract.deployByAbi(owner, sr25519CompiledСontract.abi, sr25519CompiledСontract.object);
+ expect(await sr25519Сontract.methods.verifyValid().call({from: owner})).to.be.true;
+ expect(await sr25519Сontract.methods.verifyInvalid().call({from: owner})).to.be.false;
+ });
+});
\ No newline at end of file
tests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -60,7 +60,7 @@
}
async compile(name: string, src: string, imports?: ContractImports[]): Promise<CompiledContract> {
- const out = JSON.parse(solc.compile(JSON.stringify({
+ const compiled = JSON.parse(solc.compile(JSON.stringify({
language: 'Solidity',
sources: {
[`${name}.sol`]: {
@@ -74,7 +74,18 @@
},
},
},
- }), {import: await this.findImports(imports)})).contracts[`${name}.sol`][name];
+ }), {import: await this.findImports(imports)}));
+
+ const hasErrors = compiled['errors']
+ && compiled['errors'].length > 0
+ && compiled.errors.some(function(err: any) {
+ return err.severity == 'error';
+ });
+
+ if (hasErrors) {
+ throw compiled.errors;
+ }
+ const out = compiled.contracts[`${name}.sol`][name];
return {
abi: out.abi,