From 60ea0ef4a6b104f47d74b61550a2f60d46a93c84 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Tue, 27 Jul 2021 16:33:17 +0000 Subject: [PATCH] feat: evm contract helpers pallet --- --- /dev/null +++ b/pallets/evm-contract-helpers/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "pallet-evm-contract-helpers" +version = "0.1.0" +edition = "2018" + +[dependencies] +frame-support = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.8' } +frame-system = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.8' } +sp-runtime = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.8' } +sp-std = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.8' } +sp-core = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.8' } +evm-coder = { default-features = false, path = '../../crates/evm-coder' } +pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' } +pallet-evm = { default-features = false, version = "5.0.0-dev", git = "https://github.com/uniquenetwork/frontier.git", branch = "injected-transactions-parachain" } +up-sponsorship = { default-features = false, path = '../../primitives/sponsorship' } +log = "0.4.14" + +[dependencies.codec] +default-features = false +features = ['derive'] +package = 'parity-scale-codec' +version = '2.0.0' + +[features] +default = ["std"] +std = [ + "frame-support/std", + "frame-system/std", + "sp-runtime/std", + "sp-std/std", + "sp-core/std", + "evm-coder/std", + "pallet-evm-coder-substrate/std", + "pallet-evm/std", + "up-sponsorship/std", +] --- /dev/null +++ b/pallets/evm-contract-helpers/src/eth.rs @@ -0,0 +1,150 @@ +use core::marker::PhantomData; +use evm_coder::{abi::AbiWriter, execution::Result, solidity_interface, types::*}; +use pallet_evm_coder_substrate::SubstrateRecorder; +use pallet_evm::{ExitReason, ExitRevert, OnCreate, OnMethodCall, PrecompileOutput}; +use sp_core::H160; +use crate::{ + AllowlistEnabled, Config, Owner, Pallet, SelfSponsoring, SponsorBasket, SponsoringRateLimit, +}; +use frame_support::traits::Get; +use up_sponsorship::SponsorshipHandler; +use sp_std::vec::Vec; + +struct ContractHelpers(SubstrateRecorder); + +#[solidity_interface(name = "ContractHelpers")] +impl ContractHelpers { + fn contract_owner(&self, contract: address) -> Result
{ + self.0.consume_sload()?; + Ok(>::get(contract)) + } + + fn sponsoring_enabled(&self, contract: address) -> Result { + self.0.consume_sload()?; + Ok(>::get(contract)) + } + + fn toggle_sponsoring( + &mut self, + caller: caller, + contract: address, + enabled: bool, + ) -> Result { + self.0.consume_sload()?; + >::ensure_owner(contract, caller)?; + self.0.consume_sstore()?; + >::toggle_sponsoring(contract, enabled); + Ok(()) + } + + fn allowed(&self, contract: address, user: address) -> Result { + self.0.consume_sload()?; + Ok(>::allowed(contract, user)) + } + + fn allowlist_enabled(&self, contract: address) -> Result { + self.0.consume_sload()?; + Ok(>::get(contract)) + } + + fn toggle_allowlist( + &mut self, + caller: caller, + contract: address, + enabled: bool, + ) -> Result { + self.0.consume_sload()?; + >::ensure_owner(contract, caller)?; + self.0.consume_sstore()?; + >::toggle_allowlist(contract, enabled); + Ok(()) + } + + fn toggle_allowed( + &mut self, + caller: caller, + contract: address, + user: address, + allowed: bool, + ) -> Result { + self.0.consume_sload()?; + >::ensure_owner(contract, caller)?; + self.0.consume_sstore()?; + >::toggle_allowed(contract, user, allowed); + Ok(()) + } +} + +pub struct HelpersOnMethodCall(PhantomData<*const T>); +impl OnMethodCall for HelpersOnMethodCall { + fn is_reserved(contract: &sp_core::H160) -> bool { + contract == &T::ContractAddress::get() + } + + fn is_used(contract: &sp_core::H160) -> bool { + contract == &T::ContractAddress::get() + } + + fn call( + source: &sp_core::H160, + target: &sp_core::H160, + gas_left: u64, + input: &[u8], + value: sp_core::U256, + ) -> Option { + // TODO: Extract to another OnMethodCall handler + if !>::allowed(*target, *source) { + return Some(PrecompileOutput { + exit_status: ExitReason::Revert(ExitRevert::Reverted), + cost: 0, + output: { + let mut writer = AbiWriter::new_call(evm_coder::fn_selector!(Error(string))); + writer.string("Target contract is allowlisted"); + writer.finish() + }, + logs: sp_std::vec![], + }); + } + + if target != &T::ContractAddress::get() { + return None; + } + + let mut helpers = ContractHelpers::(SubstrateRecorder::::new(*target, gas_left)); + let result = pallet_evm_coder_substrate::call_internal(*source, &mut helpers, value, input); + helpers.0.evm_to_precompile_output(result) + } + + fn get_code(contract: &sp_core::H160) -> Option> { + (contract == &T::ContractAddress::get()) + .then(|| include_bytes!("./stubs/ContractHelpers.bin").to_vec()) + } +} + +pub struct HelpersOnCreate(PhantomData<*const T>); +impl OnCreate for HelpersOnCreate { + fn on_create(owner: H160, contract: H160) { + >::insert(contract, owner); + } +} + +pub struct HelpersContractSponsoring(PhantomData<*const T>); +impl SponsorshipHandler)> for HelpersContractSponsoring { + fn get_sponsor(who: &H160, call: &(H160, Vec)) -> Option { + if >::get(&call.0) { + let block_number = >::block_number() as T::BlockNumber; + let limit = >::get(&call.0); + if let Some(last_tx_block) = >::get(&call.0, who) { + >::insert(&call.0, who, block_number); + let limit_time = last_tx_block + limit.into(); + if block_number > limit_time { + return Some(call.0); + } + } else { + >::insert(&call.0, who, block_number); + return Some(call.0); + } + } + None + } +} --- /dev/null +++ b/pallets/evm-contract-helpers/src/lib.rs @@ -0,0 +1,92 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; +pub use eth::*; +pub mod eth; + +#[frame_support::pallet] +pub mod pallet { + use evm_coder::execution::Result; + use frame_support::pallet_prelude::*; + use pallet_evm::RawEvent; + use sp_core::H160; + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config { + type Event: From> + IsType<::Event>; + + type ContractAddress: Get; + } + + #[pallet::error] + pub enum Error { + /// This method is only executable by owner + NoPermission, + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::storage] + pub(super) type Owner = + StorageMap; + + #[pallet::storage] + pub(super) type SelfSponsoring = + StorageMap; + + #[pallet::storage] + pub(super) type SponsoringRateLimit = + StorageMap; + + #[pallet::storage] + pub(super) type SponsorBasket = StorageDoubleMap< + Hasher1 = Twox128, + Key1 = H160, + Hasher2 = Twox128, + Key2 = H160, + Value = T::BlockNumber, + QueryKind = OptionQuery, + >; + + #[pallet::storage] + pub(super) type AllowlistEnabled = + StorageMap; + + #[pallet::storage] + pub(super) type Allowlist = StorageDoubleMap< + Hasher1 = Twox128, + Key1 = H160, + Hasher2 = Twox128, + Key2 = H160, + Value = bool, + QueryKind = ValueQuery, + >; + + impl Pallet { + pub fn toggle_sponsoring(contract: H160, enabled: bool) { + >::insert(contract, enabled); + } + + pub fn allowed(contract: H160, user: H160) -> bool { + if !>::get(contract) { + return true; + } + >::get(&contract, &user) || >::get(&contract) == user + } + + pub fn toggle_allowlist(contract: H160, enabled: bool) { + >::insert(contract, enabled) + } + + pub fn toggle_allowed(contract: H160, user: H160, allowed: bool) { + >::insert(contract, user, allowed); + } + + pub fn ensure_owner(contract: H160, user: H160) -> Result<()> { + ensure!(>::get(&contract) == user, "no permission"); + Ok(()) + } + } +} --- /dev/null +++ b/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol @@ -0,0 +1,40 @@ +contract ContractHelpers { + uint8 _dummmy = 0; + address _dummy_addr = 0x0000000000000000000000000000000000000000; + string stub_error = "this contract does not exists, contract helpers are implemented on substrate chain side"; + + function contractOwner(address contract_address) public view returns (address) { + require(false, stub_error); + contract_address; + return _dummy_addr; + } + + function sponsoringEnabled(address contract_address) public view returns (bool) { + require(false, stub_error); + contract_address; + _dummmy; + return false; + } + + function toggleSponsoring(address contract_address, bool enabled) public { + require(false, stub_error); + contract_address; + enabled; + _dummmy = 0; + } + + function toggleAllowlist(address contract_address, bool enabled) public { + require(false, stub_error); + contract_address; + enabled; + _dummmy = 0; + } + + function toggleAllowed(address contract_address, address user, bool allowed) public { + require(false, stub_error); + contract_address; + user; + allowed; + _dummmy = 0; + } +} \ No newline at end of file -- gitstuff