From ae46758edb692900139bd99c537e2e3c41478337 Mon Sep 17 00:00:00 2001 From: Aleksandr Krupenkin Date: Wed, 22 Sep 2021 12:47:03 +0900 Subject: [PATCH] Added staking EVM precompiles (#425) --- Cargo.lock | 16 +++++ Cargo.toml | 1 + precompiles/staking/Cargo.toml | 37 +++++++++++ precompiles/staking/Staking.sol | 21 ++++++ precompiles/staking/src/lib.rs | 101 +++++++++++++++++++++++++++++ runtime/shibuya/Cargo.toml | 21 +++--- runtime/shibuya/src/lib.rs | 2 +- runtime/shibuya/src/precompiles.rs | 14 +++- 8 files changed, 200 insertions(+), 13 deletions(-) create mode 100644 precompiles/staking/Cargo.toml create mode 100644 precompiles/staking/Staking.sol create mode 100644 precompiles/staking/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index acc7af7152..48ed2a95fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5759,6 +5759,21 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-precompile-staking" +version = "0.1.0" +dependencies = [ + "evm", + "frame-support", + "frame-system", + "pallet-collator-selection", + "pallet-evm", + "pallet-session", + "parity-scale-codec", + "sp-core", + "sp-std", +] + [[package]] name = "pallet-proxy" version = "4.0.0-dev" @@ -9596,6 +9611,7 @@ dependencies = [ "pallet-evm-precompile-simple", "pallet-identity", "pallet-multisig", + "pallet-precompile-staking", "pallet-session", "pallet-sudo", "pallet-timestamp", diff --git a/Cargo.toml b/Cargo.toml index e3969bfd74..821d6a052a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "runtime/shibuya", "frame/block-reward", "frame/custom-signatures", + "precompiles/staking", ] [profile.release] diff --git a/precompiles/staking/Cargo.toml b/precompiles/staking/Cargo.toml new file mode 100644 index 0000000000..d7cb9dfa63 --- /dev/null +++ b/precompiles/staking/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "pallet-precompile-staking" +version = "0.1.0" +authors = ["Stake Technologies "] +edition = "2018" +license = "Apache-2.0" +homepage = "https://astar.network" +repository = "https://github.com/PlasmNetwork/Astar" +description = "Collator staking EVM precompiles" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } +evm = { version="0.30.0", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } +pallet-evm = { git="https://github.com/PlasmNetwork/frontier", branch = "polkadot-v0.9.9", default-features = false } +pallet-session = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } +pallet-collator-selection = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.9", default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "evm/std", + "sp-std/std", + "sp-core/std", + "frame-support/std", + "frame-system/std", + "pallet-evm/std", + "pallet-session/std", + "pallet-collator-selection/std", +] diff --git a/precompiles/staking/Staking.sol b/precompiles/staking/Staking.sol new file mode 100644 index 0000000000..5049de2d0b --- /dev/null +++ b/precompiles/staking/Staking.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: BSD-3-Clause + +pragma solidity >=0.7.0; + +interface Staking { + /* + * @dev Set session keys of function caller. + */ + function set_keys(bytes calldata keys) external; + + /* + * @dev Removes any session keys of the function caller. + */ + function purge_keys() external; + + /* + * @dev Register function caller as collation candidate. + * @note Collation staking deposit will be locked. + */ + function register_as_candidate() external; +} diff --git a/precompiles/staking/src/lib.rs b/precompiles/staking/src/lib.rs new file mode 100644 index 0000000000..8067fbed9a --- /dev/null +++ b/precompiles/staking/src/lib.rs @@ -0,0 +1,101 @@ +//! Astar collator staking interface. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::Decode; +use evm::{executor::PrecompileOutput, Context, ExitError, ExitSucceed}; +use frame_support::dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}; +use pallet_evm::{AddressMapping, GasWeightMapping, Precompile}; +use sp_std::{marker::PhantomData, vec::Vec}; + +pub struct Staking(PhantomData); + +impl Staking +where + R: pallet_session::Config + pallet_collator_selection::Config, + R::Call: From> + From>, +{ + fn set_keys(keys: Vec) -> Result { + let keys = ::Keys::decode(&mut &keys[..]) + .map_err(|_| ExitError::Other("Unable to decode session keys".into()))?; + Ok(pallet_session::Call::::set_keys(keys, Default::default()).into()) + } + + fn purge_keys() -> R::Call { + pallet_session::Call::::purge_keys().into() + } + + fn register_as_candidate() -> R::Call { + pallet_collator_selection::Call::::register_as_candidate().into() + } +} + +impl Precompile for Staking +where + R: pallet_evm::Config + pallet_session::Config + pallet_collator_selection::Config, + R::Call: From> + + From> + + Dispatchable + + GetDispatchInfo, + ::Origin: From>, +{ + fn execute( + input: &[u8], + target_gas: Option, + context: &Context, + ) -> Result { + const SELECTOR_SIZE_BYTES: usize = 4; + + if input.len() < SELECTOR_SIZE_BYTES { + return Err(ExitError::Other("input length less than 4 bytes".into())); + } + + // ======= Staking.sol:Staking ======= + // Function signatures: + // bcb24ddc: set_keys(bytes) + // 321c9b7a: purge_keys() + // d09b6ba5: register_as_candidate() + let call = match input[0..SELECTOR_SIZE_BYTES] { + [0xbc, 0xb2, 0x4d, 0xdc] => { + if input.len() < SELECTOR_SIZE_BYTES + 32 * 2 { + return Err(ExitError::Other("input length less than 36 bytes".into())); + } + // Low level argument parsing + let len_offset = SELECTOR_SIZE_BYTES + 32; + let keys_offset = len_offset + 32; + let keys_len = sp_core::U256::from_big_endian(&input[len_offset..keys_offset]); + let keys = input[keys_offset..(keys_offset + keys_len.as_usize())].to_vec(); + Self::set_keys(keys)? + } + [0x32, 0x1c, 0x9b, 0x7a] => Self::purge_keys(), + [0xd0, 0x9b, 0x6b, 0xa5] => Self::register_as_candidate(), + _ => { + return Err(ExitError::Other( + "No method at selector given selector".into(), + )) + } + }; + + let info = call.get_dispatch_info(); + if let Some(gas_limit) = target_gas { + let required_gas = R::GasWeightMapping::weight_to_gas(info.weight); + if required_gas > gas_limit { + return Err(ExitError::OutOfGas); + } + } + + let origin = R::AddressMapping::into_account_id(context.caller); + let post_info = call + .dispatch(Some(origin).into()) + .map_err(|_| ExitError::Other("Method call via EVM failed".into()))?; + + let gas_used = + R::GasWeightMapping::weight_to_gas(post_info.actual_weight.unwrap_or(info.weight)); + Ok(PrecompileOutput { + exit_status: ExitSucceed::Stopped, + cost: gas_used, + output: Default::default(), + logs: Default::default(), + }) + } +} diff --git a/runtime/shibuya/Cargo.toml b/runtime/shibuya/Cargo.toml index a8d35fe9b7..43d33737a1 100644 --- a/runtime/shibuya/Cargo.toml +++ b/runtime/shibuya/Cargo.toml @@ -8,7 +8,7 @@ build = "build.rs" [dependencies] # third-party dependencies codec = { package = "parity-scale-codec", version = "2.0", default-features = false, features = ["derive"] } -evm = { version="0.30.0", default-features=false, features=["with-codec"] } +evm = { version = "0.30.0", default-features = false, features = ["with-codec"] } smallvec = "1.6.1" # primitives @@ -32,16 +32,16 @@ frame-executive = { git = "https://github.com/paritytech/substrate", branch = "p pallet-authorship = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } pallet-aura = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } -pallet-ethereum = { git="https://github.com/PlasmNetwork/frontier", branch="polkadot-v0.9.9", default-features=false } -pallet-evm = { git="https://github.com/PlasmNetwork/frontier", branch="polkadot-v0.9.9", default-features=false } -pallet-evm-precompile-bn128 = { git="https://github.com/PlasmNetwork/frontier", branch="polkadot-v0.9.9", default-features=false } -pallet-evm-precompile-dispatch = { git="https://github.com/PlasmNetwork/frontier", branch="polkadot-v0.9.9", default-features=false } -pallet-evm-precompile-modexp = { git="https://github.com/PlasmNetwork/frontier", branch="polkadot-v0.9.9", default-features=false } -pallet-evm-precompile-simple = { git="https://github.com/PlasmNetwork/frontier", branch="polkadot-v0.9.9", default-features=false } -pallet-evm-precompile-sha3fips = { git="https://github.com/PlasmNetwork/frontier", branch="polkadot-v0.9.9", default-features=false } +pallet-ethereum = { git = "https://github.com/PlasmNetwork/frontier", branch = "polkadot-v0.9.9", default-features = false } +pallet-evm = { git = "https://github.com/PlasmNetwork/frontier", branch = "polkadot-v0.9.9", default-features = false } +pallet-evm-precompile-bn128 = { git = "https://github.com/PlasmNetwork/frontier", branch = "polkadot-v0.9.9", default-features = false } +pallet-evm-precompile-dispatch = { git = "https://github.com/PlasmNetwork/frontier", branch = "polkadot-v0.9.9", default-features = false } +pallet-evm-precompile-modexp = { git = "https://github.com/PlasmNetwork/frontier", branch = "polkadot-v0.9.9", default-features = false } +pallet-evm-precompile-simple = { git = "https://github.com/PlasmNetwork/frontier", branch = "polkadot-v0.9.9", default-features = false } +pallet-evm-precompile-sha3fips = { git = "https://github.com/PlasmNetwork/frontier", branch = "polkadot-v0.9.9", default-features = false } pallet-identity = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } pallet-multisig = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } -pallet-session = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false, features = ["historical"] } +pallet-session = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } pallet-utility = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } pallet-vesting = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9", default-features = false } @@ -69,8 +69,10 @@ xcm-executor = { git = "https://github.com/paritytech/polkadot", default-feature xcm-builder = { git = "https://github.com/paritytech/polkadot", default-features = false , branch = "release-v0.9.9" } xcm = { git = "https://github.com/paritytech/polkadot", default-features = false , branch = "release-v0.9.9" } +# Astar pallets pallet-block-reward = { path = "../../frame/block-reward", default-features = false } pallet-custom-signatures = { path = "../../frame/custom-signatures", default-features = false } +pallet-precompile-staking = { path = "../../precompiles/staking", default-features = false } [build-dependencies] substrate-wasm-builder = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9" } @@ -110,6 +112,7 @@ std = [ "pallet-evm-precompile-sha3fips/std", "pallet-identity/std", "pallet-multisig/std", + "pallet-precompile-staking/std", "pallet-session/std", "pallet-utility/std", "pallet-timestamp/std", diff --git a/runtime/shibuya/src/lib.rs b/runtime/shibuya/src/lib.rs index cb6c601c37..5235a1762f 100644 --- a/runtime/shibuya/src/lib.rs +++ b/runtime/shibuya/src/lib.rs @@ -90,7 +90,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("shibuya"), impl_name: create_runtime_str!("shibuya"), authoring_version: 1, - spec_version: 5, + spec_version: 6, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/runtime/shibuya/src/precompiles.rs b/runtime/shibuya/src/precompiles.rs index dd21f9df91..1beaa204fd 100644 --- a/runtime/shibuya/src/precompiles.rs +++ b/runtime/shibuya/src/precompiles.rs @@ -11,6 +11,7 @@ use pallet_evm_precompile_dispatch::Dispatch; use pallet_evm_precompile_modexp::Modexp; use pallet_evm_precompile_sha3fips::Sha3FIPS256; use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; +use pallet_precompile_staking::Staking; use sp_core::H160; use sp_std::fmt::Debug; use sp_std::marker::PhantomData; @@ -23,7 +24,7 @@ impl ShibuyaNetworkPrecompiles { /// Return all addresses that contain precompiles. This can be used to populate dummy code /// under the precompile. pub fn used_addresses>() -> impl Iterator { - sp_std::vec![1, 2, 3, 4, 5, 6, 7, 8, 1024, 1025, 1026] + sp_std::vec![1, 2, 3, 4, 5, 6, 7, 8, 1024, 1025, 1026, 20480] .into_iter() .map(|x| hash(x).into()) } @@ -32,10 +33,15 @@ impl ShibuyaNetworkPrecompiles { /// The following distribution has been decided for the precompiles /// 0-1023: Ethereum Mainnet Precompiles /// 1024-2047 Precompiles that are not in Ethereum Mainnet -impl PrecompileSet for ShibuyaNetworkPrecompiles +impl PrecompileSet for ShibuyaNetworkPrecompiles where - R::Call: Dispatchable + GetDispatchInfo + Decode, + R: pallet_evm::Config + pallet_session::Config + pallet_collator_selection::Config, ::Origin: From>, + R::Call: From> + + From> + + Dispatchable + + GetDispatchInfo + + Decode, { fn execute( address: H160, @@ -57,6 +63,8 @@ where a if a == hash(1024) => Some(Sha3FIPS256::execute(input, target_gas, context)), a if a == hash(1025) => Some(Dispatch::::execute(input, target_gas, context)), a if a == hash(1026) => Some(ECRecoverPublicKey::execute(input, target_gas, context)), + // Astar precompiles (starts from 0x5000): + a if a == hash(20480) => Some(Staking::::execute(input, target_gas, context)), // Default _ => None, }