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.rsdiffbeforeafterboth1// Copyright 2019-2022 PureStake Inc.2// Copyright 2022 Stake Technologies3// This file is part of Utils package, originally developed by Purestake Inc.4// Utils package used in Astar Network in terms of GPLv3.5//6// Utils is free software: you can redistribute it and/or modify7// it under the terms of the GNU General Public License as published by8// the Free Software Foundation, either version 3 of the License, or9// (at your option) any later version.1011// Utils is distributed in the hope that it will be useful,12// but WITHOUT ANY WARRANTY; without even the implied warranty of13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the14// GNU General Public License for more details.1516// You should have received a copy of the GNU General Public License17// along with Utils. If not, see <http://www.gnu.org/licenses/>.1819use super::{EvmResult, Gasometer};2021use sp_std::borrow::ToOwned;22use core::{any::type_name, ops::Range};23use sp_core::{H160, H256, U256};24use sp_std::{convert::TryInto, vec, vec::Vec};2526/// The `address` type of Solidity.27/// H160 could represent 2 types of data (bytes20 and address) that are not encoded the same way.28/// To avoid issues writing H160 is thus not supported.29#[derive(Clone, Copy, Debug, Eq, PartialEq)]30pub struct Address(pub H160);3132impl From<H160> for Address {33 fn from(a: H160) -> Address {34 Address(a)35 }36}3738impl From<Address> for H160 {39 fn from(a: Address) -> H160 {40 a.041 }42}4344/// The `bytes`/`string` type of Solidity.45/// It is different from `Vec<u8>` which will be serialized with padding for each `u8` element46/// of the array, while `Bytes` is tightly packed.47#[derive(Clone, Debug, Eq, PartialEq)]48pub struct Bytes(pub Vec<u8>);4950impl From<&[u8]> for Bytes {51 fn from(a: &[u8]) -> Self {52 Self(a.to_owned())53 }54}5556impl From<&str> for Bytes {57 fn from(a: &str) -> Self {58 a.as_bytes().into()59 }60}6162impl Into<Vec<u8>> for Bytes {63 fn into(self: Self) -> Vec<u8> {64 self.065 }66}6768/// Wrapper around an EVM input slice, helping to parse it.69/// Provide functions to parse common types.70#[derive(Clone, Copy, Debug)]71pub struct EvmDataReader<'a> {72 input: &'a [u8],73 cursor: usize,74}7576impl<'a> EvmDataReader<'a> {77 /// Create a new input parser.78 pub fn new(input: &'a [u8]) -> Self {79 Self { input, cursor: 0 }80 }8182 /// Create a new input parser from a selector-initial input.83 pub fn new_with_selector<T>(gasometer: &Gasometer, input: &'a [u8]) -> EvmResult<(Self, T)>84 where85 T: num_enum::TryFromPrimitive<Primitive = u32>,86 {87 if input.len() < 4 {88 return Err(gasometer.revert("tried to parse selector out of bounds"));89 }9091 let mut buffer = [0u8; 4];92 buffer.copy_from_slice(&input[0..4]);93 let selector = T::try_from_primitive(u32::from_be_bytes(buffer)).map_err(|_| {94 log::trace!(95 target: "precompile-utils",96 "Failed to match function selector for {}",97 type_name::<T>()98 );99 gasometer.revert("unknown selector")100 })?;101102 Ok((Self::new(&input[4..]), selector))103 }104105 /// Check the input has at least the correct amount of arguments before the end (32 bytes values).106 pub fn expect_arguments(&self, gasometer: &Gasometer, args: usize) -> EvmResult {107 if self.input.len() >= self.cursor + args * 32 {108 Ok(())109 } else {110 Err(gasometer.revert("input doesn't match expected length"))111 }112 }113114 /// Read data from the input.115 /// Must be provided a gasometer to generate correct Revert errors.116 /// TODO : Benchmark and add cost of parsing to gasometer ?117 pub fn read<T: EvmData>(&mut self, gasometer: &Gasometer) -> EvmResult<T> {118 T::read(self, gasometer)119 }120121 /// Reads a pointer, returning a reader targetting the pointed location.122 pub fn read_pointer(&mut self, gasometer: &Gasometer) -> EvmResult<Self> {123 let offset: usize = self124 .read::<U256>(gasometer)125 .map_err(|_| gasometer.revert("tried to parse array offset out of bounds"))?126 .try_into()127 .map_err(|_| gasometer.revert("array offset is too large"))?;128129 if offset >= self.input.len() {130 return Err(gasometer.revert("pointer points out of bounds"));131 }132133 Ok(Self {134 input: &self.input[offset..],135 cursor: 0,136 })137 }138139 /// Move the reading cursor with provided length, and return a range from the previous cursor140 /// location to the new one.141 /// Checks cursor overflows.142 fn move_cursor(&mut self, gasometer: &Gasometer, len: usize) -> EvmResult<Range<usize>> {143 let start = self.cursor;144 let end = self145 .cursor146 .checked_add(len)147 .ok_or_else(|| gasometer.revert("data reading cursor overflow"))?;148149 self.cursor = end;150151 Ok(start..end)152 }153}154155/// Help build an EVM input/output data.156///157/// Functions takes `self` to allow chaining all calls like158/// `EvmDataWriter::new().write(...).write(...).build()`.159/// While it could be more ergonomic to take &mut self, this would160/// prevent to have a `build` function that don't clone the output.161#[derive(Clone, Debug)]162pub struct EvmDataWriter {163 pub(crate) data: Vec<u8>,164 offset_data: Vec<OffsetDatum>,165 selector: Option<u32>,166}167168#[derive(Clone, Debug)]169struct OffsetDatum {170 // Offset location in the container data.171 offset_position: usize,172 // Data pointed by the offset that must be inserted at the end of container data.173 data: Vec<u8>,174 // Inside of arrays, the offset is not from the start of array data (length), but from the start175 // of the item. This shift allow to correct this.176 offset_shift: usize,177}178179impl EvmDataWriter {180 /// Creates a new empty output builder (without selector).181 pub fn new() -> Self {182 Self {183 data: vec![],184 offset_data: vec![],185 selector: None,186 }187 }188189 /// Return the built data.190 pub fn build(mut self) -> Vec<u8> {191 Self::bake_offsets(&mut self.data, self.offset_data);192193 if let Some(selector) = self.selector {194 let mut output = selector.to_be_bytes().to_vec();195 output.append(&mut self.data);196 output197 } else {198 self.data199 }200 }201202 /// Add offseted data at the end of this writer's data, updating the offsets.203 fn bake_offsets(output: &mut Vec<u8>, offsets: Vec<OffsetDatum>) {204 for mut offset_datum in offsets {205 let offset_position = offset_datum.offset_position;206 let offset_position_end = offset_position + 32;207208 // The offset is the distance between the start of the data and the209 // start of the pointed data (start of a struct, length of an array).210 // Offsets in inner data are relative to the start of their respective "container".211 // However in arrays the "container" is actually the item itself instead of the whole212 // array, which is corrected by `offset_shift`.213 let free_space_offset = output.len() - offset_datum.offset_shift;214215 // Override dummy offset to the offset it will be in the final output.216 U256::from(free_space_offset)217 .to_big_endian(&mut output[offset_position..offset_position_end]);218219 // Append this data at the end of the current output.220 output.append(&mut offset_datum.data);221 }222 }223224 /// Write arbitrary bytes.225 /// Doesn't handle any alignement checks, prefer using `write` instead if possible.226 fn write_raw_bytes(mut self, value: &[u8]) -> Self {227 self.data.extend_from_slice(value);228 self229 }230231 /// Write data of requested type.232 pub fn write<T: EvmData>(mut self, value: T) -> Self {233 T::write(&mut self, value);234 self235 }236237 /// Writes a pointer to given data.238 /// The data will be appended when calling `build`.239 /// Initially write a dummy value as offset in this writer's data, which will be replaced by240 /// the correct offset once the pointed data is appended.241 ///242 /// Takes `&mut self` since its goal is to be used inside `EvmData` impl and not in chains.243 pub fn write_pointer(&mut self, data: Vec<u8>) {244 let offset_position = self.data.len();245 H256::write(self, H256::repeat_byte(0xff));246247 self.offset_data.push(OffsetDatum {248 offset_position,249 data,250 offset_shift: 0,251 });252 }253}254255impl Default for EvmDataWriter {256 fn default() -> Self {257 Self::new()258 }259}260261/// Data that can be converted from and to EVM data types.262pub trait EvmData: Sized {263 fn read(reader: &mut EvmDataReader, gasometer: &Gasometer) -> EvmResult<Self>;264 fn write(writer: &mut EvmDataWriter, value: Self);265}266267impl EvmData for H256 {268 fn read(reader: &mut EvmDataReader, gasometer: &Gasometer) -> EvmResult<Self> {269 let range = reader.move_cursor(gasometer, 32)?;270271 let data = reader272 .input273 .get(range)274 .ok_or_else(|| gasometer.revert("tried to parse H256 out of bounds"))?;275276 Ok(H256::from_slice(data))277 }278279 fn write(writer: &mut EvmDataWriter, value: Self) {280 writer.data.extend_from_slice(value.as_bytes());281 }282}283284impl EvmData for Address {285 fn read(reader: &mut EvmDataReader, gasometer: &Gasometer) -> EvmResult<Self> {286 let range = reader.move_cursor(gasometer, 32)?;287288 let data = reader289 .input290 .get(range)291 .ok_or_else(|| gasometer.revert("tried to parse H160 out of bounds"))?;292293 Ok(H160::from_slice(&data[12..32]).into())294 }295296 fn write(writer: &mut EvmDataWriter, value: Self) {297 H256::write(writer, value.0.into());298 }299}300301impl EvmData for U256 {302 fn read(reader: &mut EvmDataReader, gasometer: &Gasometer) -> EvmResult<Self> {303 let range = reader.move_cursor(gasometer, 32)?;304305 let data = reader306 .input307 .get(range)308 .ok_or_else(|| gasometer.revert("tried to parse U256 out of bounds"))?;309310 Ok(U256::from_big_endian(data))311 }312313 fn write(writer: &mut EvmDataWriter, value: Self) {314 let mut buffer = [0u8; 32];315 value.to_big_endian(&mut buffer);316 writer.data.extend_from_slice(&buffer);317 }318}319320macro_rules! impl_evmdata_for_uints {321 ($($uint:ty, )*) => {322 $(323 impl EvmData for $uint {324 fn read(reader: &mut EvmDataReader, gasometer: &Gasometer) -> EvmResult<Self> {325 let range = reader.move_cursor(gasometer, 32)?;326327 let data = reader328 .input329 .get(range)330 .ok_or_else(|| gasometer.revert(alloc::format!(331 "tried to parse {} out of bounds", core::any::type_name::<Self>()332 )))?;333334 let mut buffer = [0u8; core::mem::size_of::<Self>()];335 buffer.copy_from_slice(&data[32 - core::mem::size_of::<Self>()..]);336 Ok(Self::from_be_bytes(buffer))337 }338339 fn write(writer: &mut EvmDataWriter, value: Self) {340 let mut buffer = [0u8; 32];341 buffer[32 - core::mem::size_of::<Self>()..].copy_from_slice(&value.to_be_bytes());342 writer.data.extend_from_slice(&buffer);343 }344 }345 )*346 };347}348349impl_evmdata_for_uints!(u16, u32, u64, u128,);350351// The implementation for u8 is specific, for performance reasons.352impl EvmData for u8 {353 fn read(reader: &mut EvmDataReader, gasometer: &Gasometer) -> EvmResult<Self> {354 let range = reader.move_cursor(gasometer, 32)?;355356 let data = reader357 .input358 .get(range)359 .ok_or_else(|| gasometer.revert("tried to parse u64 out of bounds"))?;360361 Ok(data[31])362 }363364 fn write(writer: &mut EvmDataWriter, value: Self) {365 let mut buffer = [0u8; 32];366 buffer[31] = value;367368 writer.data.extend_from_slice(&buffer);369 }370}371372impl EvmData for bool {373 fn read(reader: &mut EvmDataReader, gasometer: &Gasometer) -> EvmResult<Self> {374 let h256 = H256::read(reader, gasometer)375 .map_err(|_| gasometer.revert("tried to parse bool out of bounds"))?;376377 Ok(!h256.is_zero())378 }379380 fn write(writer: &mut EvmDataWriter, value: Self) {381 let mut buffer = [0u8; 32];382 if value {383 buffer[31] = 1;384 }385386 writer.data.extend_from_slice(&buffer);387 }388}389390impl<T: EvmData> EvmData for Vec<T> {391 fn read(reader: &mut EvmDataReader, gasometer: &Gasometer) -> EvmResult<Self> {392 let mut inner_reader = reader.read_pointer(gasometer)?;393394 let array_size: usize = inner_reader395 .read::<U256>(gasometer)396 .map_err(|_| gasometer.revert("tried to parse array length out of bounds"))?397 .try_into()398 .map_err(|_| gasometer.revert("array length is too large"))?;399400 let mut array = vec![];401402 let mut item_reader = EvmDataReader {403 input: inner_reader404 .input405 .get(32..)406 .ok_or_else(|| gasometer.revert("try to read array items out of bound"))?,407 cursor: 0,408 };409410 for _ in 0..array_size {411 array.push(item_reader.read(gasometer)?);412 }413414 Ok(array)415 }416417 fn write(writer: &mut EvmDataWriter, value: Self) {418 let mut inner_writer = EvmDataWriter::new().write(U256::from(value.len()));419420 for inner in value {421 // Any offset in items are relative to the start of the item instead of the422 // start of the array. However if there is offseted data it must but appended after423 // all items (offsets) are written. We thus need to rely on `compute_offsets` to do424 // that, and must store a "shift" to correct the offsets.425 let shift = inner_writer.data.len();426 let item_writer = EvmDataWriter::new().write(inner);427428 inner_writer = inner_writer.write_raw_bytes(&item_writer.data);429 for mut offset_datum in item_writer.offset_data {430 offset_datum.offset_shift += 32;431 offset_datum.offset_position += shift;432 inner_writer.offset_data.push(offset_datum);433 }434 }435436 writer.write_pointer(inner_writer.build());437 }438}439440impl EvmData for Bytes {441 fn read(reader: &mut EvmDataReader, gasometer: &Gasometer) -> EvmResult<Self> {442 let mut inner_reader = reader.read_pointer(gasometer)?;443444 // Read bytes/string size.445 let array_size: usize = inner_reader446 .read::<U256>(gasometer)447 .map_err(|_| gasometer.revert("tried to parse bytes/string length out of bounds"))?448 .try_into()449 .map_err(|_| gasometer.revert("bytes/string length is too large"))?;450451 // Get valid range over the bytes data.452 let range = inner_reader.move_cursor(gasometer, array_size)?;453454 let data = inner_reader455 .input456 .get(range)457 .ok_or_else(|| gasometer.revert("tried to parse bytes/string out of bounds"))?;458459 let bytes = Self(data.to_owned());460461 Ok(bytes)462 }463464 fn write(writer: &mut EvmDataWriter, value: Self) {465 let length = value.0.len();466467 // Pad the data.468 // Leave it as is if a multiple of 32, otherwise pad to next469 // multiple or 32.470 let chunks = length / 32;471 let padded_size = match length % 32 {472 0 => chunks * 32,473 _ => (chunks + 1) * 32,474 };475476 let mut value = value.0.to_vec();477 value.resize(padded_size, 0);478479 writer.write_pointer(480 EvmDataWriter::new()481 .write(U256::from(length))482 .write_raw_bytes(&value)483 .build(),484 );485 }486}runtime/common/ethereum/precompiles/utils/macro/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/runtime/common/ethereum/precompiles/utils/macro/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "precompile-utils-macro"
+authors = [ "StakeTechnologies", "PureStake" ]
+description = ""
+edition = "2018"
+version = "0.1.0"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+num_enum = { version = "0.5.3", default-features = false }
+proc-macro2 = "1.0"
+quote = "1.0"
+sha3 = "0.8"
+syn = { version = "1.0", features = [ "extra-traits", "fold", "full", "visit" ] }
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,