diff --git a/Cargo.lock b/Cargo.lock index 235e35f9..4a82fb35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7919,6 +7919,63 @@ dependencies = [ "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.7.0)", ] +[[package]] +name = "pallet-evm-precompile-services" +version = "0.1.0" +dependencies = [ + "derive_more 1.0.0", + "ethereum", + "ethers", + "fp-account", + "fp-consensus", + "fp-dynamic-fee", + "fp-ethereum", + "fp-evm", + "fp-rpc", + "fp-self-contained", + "fp-storage", + "frame-election-provider-support", + "frame-support", + "frame-system", + "hex", + "hex-literal 0.4.1", + "libsecp256k1", + "num_enum", + "pallet-assets", + "pallet-balances", + "pallet-base-fee", + "pallet-dynamic-fee", + "pallet-ethereum", + "pallet-evm", + "pallet-evm-chain-id", + "pallet-evm-precompile-blake2", + "pallet-evm-precompile-bn128", + "pallet-evm-precompile-curve25519", + "pallet-evm-precompile-ed25519", + "pallet-evm-precompile-modexp", + "pallet-evm-precompile-sha3fips", + "pallet-evm-precompile-simple", + "pallet-multi-asset-delegation", + "pallet-services", + "pallet-session", + "pallet-staking", + "pallet-timestamp", + "parity-scale-codec", + "precompile-utils", + "scale-info", + "serde", + "serde_json", + "sha3", + "smallvec", + "sp-core", + "sp-io", + "sp-keystore", + "sp-runtime", + "sp-staking", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.7.0)", + "tangle-primitives", +] + [[package]] name = "pallet-evm-precompile-sha3fips" version = "2.0.0-dev" @@ -14713,6 +14770,7 @@ dependencies = [ "pallet-evm-precompile-preimage", "pallet-evm-precompile-proxy", "pallet-evm-precompile-registry", + "pallet-evm-precompile-services", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", "pallet-evm-precompile-staking", diff --git a/Cargo.toml b/Cargo.toml index ae967f70..83b90210 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ members = [ "precompiles/verify-schnorr-signatures", "precompiles/verify-bls381-signature", "precompiles/multi-asset-delegation", + "precompiles/services", "tangle-subxt", "evm-tracer", ] @@ -316,6 +317,7 @@ pallet-evm-precompile-verify-ecdsa-secp256r1-signature = { path = "precompiles/v pallet-evm-precompile-verify-ecdsa-stark-signature = { path = "precompiles/verify-ecdsa-stark-signature", default-features = false } pallet-evm-precompile-verify-schnorr-signatures = { path = "precompiles/verify-schnorr-signatures", default-features = false } pallet-evm-precompile-verify-bls381-signature = { path = "precompiles/verify-bls381-signature", default-features = false } +pallet-evm-precompile-services = { path = "precompiles/services", default-features = false } # Precompiles utils postcard = { version = "1", default-features = false } diff --git a/pallets/services/src/lib.rs b/pallets/services/src/lib.rs index e7592a2f..bff2e036 100644 --- a/pallets/services/src/lib.rs +++ b/pallets/services/src/lib.rs @@ -32,7 +32,7 @@ use sp_runtime::{traits::Get, DispatchResult}; mod functions; mod impls; mod rpc; -mod traits; +pub mod traits; pub mod types; #[cfg(test)] diff --git a/precompiles/services/Cargo.toml b/precompiles/services/Cargo.toml new file mode 100644 index 00000000..b096e7e2 --- /dev/null +++ b/precompiles/services/Cargo.toml @@ -0,0 +1,137 @@ +[package] +name = "pallet-evm-precompile-services" +version = "0.1.0" +authors = { workspace = true } +edition = "2021" +description = "A Precompile to make pallet-services calls encoding accessible to pallet-evm" + +[dependencies] + +# Moonbeam +precompile-utils = { workspace = true } + +# Substrate +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-balances = { workspace = true } +pallet-multi-asset-delegation = { workspace = true } +parity-scale-codec = { workspace = true, features = ["derive"] } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +# Frontier +fp-evm = { workspace = true } +pallet-evm = { workspace = true, features = ["forbid-evm-reentrancy"] } +tangle-primitives = { workspace = true } +pallet-services = { workspace = true } + +[dev-dependencies] +derive_more = { workspace = true } +hex-literal = { workspace = true } +serde = { workspace = true } +sha3 = { workspace = true } +ethereum = { workspace = true, features = ["with-codec"] } +ethers = "2.0" +hex = { workspace = true } +num_enum = { workspace = true } +libsecp256k1 = { workspace = true } +serde_json = { workspace = true } +smallvec = { workspace = true } +sp-keystore = { workspace = true } + +# Moonbeam +precompile-utils = { workspace = true, features = ["std", "testing"] } + +# Substrate +pallet-balances = { workspace = true, features = ["std"] } +pallet-assets = { workspace = true, features = ["std"] } +pallet-timestamp = { workspace = true, features = ["std"] } +scale-info = { workspace = true, features = ["derive", "std"] } +sp-io = { workspace = true, features = ["std"] } + +# Frontier Primitive +fp-account = { workspace = true } +fp-consensus = { workspace = true } +fp-dynamic-fee = { workspace = true } +fp-ethereum = { workspace = true } +fp-rpc = { workspace = true } +fp-self-contained = { workspace = true } +fp-storage = { workspace = true } + +# Frontier FRAME +pallet-base-fee = { workspace = true } +pallet-dynamic-fee = { workspace = true } +pallet-ethereum = { workspace = true } +pallet-evm = { workspace = true } +pallet-evm-chain-id = { workspace = true } + +pallet-evm-precompile-blake2 = { workspace = true } +pallet-evm-precompile-bn128 = { workspace = true } +pallet-evm-precompile-curve25519 = { workspace = true } +pallet-evm-precompile-ed25519 = { workspace = true } +pallet-evm-precompile-modexp = { workspace = true } +pallet-evm-precompile-sha3fips = { workspace = true } +pallet-evm-precompile-simple = { workspace = true } + +pallet-session = { workspace = true } +pallet-staking = { workspace = true } +sp-staking = { workspace = true } +frame-election-provider-support = { workspace = true } + +[features] +default = ["std"] +std = [ + "fp-evm/std", + "frame-support/std", + "frame-system/std", + "pallet-balances/std", + "pallet-evm/std", + "pallet-multi-asset-delegation/std", + "parity-scale-codec/std", + "precompile-utils/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", + "tangle-primitives/std", + "pallet-assets/std", + "pallet-services/std", + "hex/std", + "scale-info/std", + "sp-runtime/std", + "frame-support/std", + "frame-system/std", + "sp-core/std", + "sp-std/std", + "sp-io/std", + "tangle-primitives/std", + "pallet-balances/std", + "pallet-timestamp/std", + "fp-account/std", + "fp-consensus/std", + "fp-dynamic-fee/std", + "fp-ethereum/std", + "fp-evm/std", + "fp-rpc/std", + "fp-self-contained/std", + "fp-storage/std", + "pallet-base-fee/std", + "pallet-dynamic-fee/std", + "pallet-ethereum/std", + "pallet-evm/std", + "pallet-evm-chain-id/std", + + "pallet-evm-precompile-modexp/std", + "pallet-evm-precompile-sha3fips/std", + "pallet-evm-precompile-simple/std", + "pallet-evm-precompile-blake2/std", + "pallet-evm-precompile-bn128/std", + "pallet-evm-precompile-curve25519/std", + "pallet-evm-precompile-ed25519/std", + "precompile-utils/std", + "serde/std", + "pallet-session/std", + "pallet-staking/std", + "sp-staking/std", + "frame-election-provider-support/std", +] diff --git a/precompiles/services/Services.sol b/precompiles/services/Services.sol new file mode 100644 index 00000000..b4056052 --- /dev/null +++ b/precompiles/services/Services.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +/// @title ServicesPrecompile Interface +/// @dev This interface is meant to interact with the ServicesPrecompile in the Tangle network. +interface ServicesPrecompile { + + /// @notice Create a new service blueprint + /// @param blueprint_data The blueprint data encoded as bytes + function createBlueprint(bytes calldata blueprint_data) external; + + /// @notice Register an operator for a specific blueprint + /// @param blueprint_id The ID of the blueprint to register for + /// @param preferences The operator's preferences encoded as bytes + /// @param registration_args The registration arguments encoded as bytes + function registerOperator(uint256 blueprint_id, bytes calldata preferences, bytes calldata registration_args) external; + + /// @notice Unregister an operator from a specific blueprint + /// @param blueprint_id The ID of the blueprint to unregister from + function unregisterOperator(uint256 blueprint_id) external; + + /// @notice Request a service from a specific blueprint + /// @param blueprint_id The ID of the blueprint + /// @param permitted_callers_data The permitted callers for the service encoded as bytes + /// @param service_providers_data The service providers encoded as bytes + /// @param request_args_data The request arguments encoded as bytes + function requestService(uint256 blueprint_id, bytes calldata permitted_callers_data, bytes calldata service_providers_data, bytes calldata request_args_data) external; + + /// @notice Terminate a service + /// @param service_id The ID of the service to terminate + function terminateService(uint256 service_id) external; + + /// @notice Approve a service request + /// @param request_id The ID of the service request to approve + function approve(uint256 request_id) external; + + /// @notice Reject a service request + /// @param request_id The ID of the service request to reject + function reject(uint256 request_id) external; + + /// @notice Call a job in the service + /// @param service_id The ID of the service + /// @param job The job index (as uint8) + /// @param args_data The arguments of the job encoded as bytes + function callJob(uint256 service_id, uint8 job, bytes calldata args_data) external; + + /// @notice Submit the result of a job call + /// @param service_id The ID of the service + /// @param call_id The ID of the call + /// @param result_data The result data encoded as bytes + function submitResult(uint256 service_id, uint256 call_id, bytes calldata result_data) external; +} diff --git a/precompiles/services/src/lib.rs b/precompiles/services/src/lib.rs new file mode 100644 index 00000000..bb1fe36d --- /dev/null +++ b/precompiles/services/src/lib.rs @@ -0,0 +1,249 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use fp_evm::PrecompileHandle; +use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; +use pallet_evm::AddressMapping; +use parity_scale_codec::Decode; +use precompile_utils::prelude::*; +use sp_core::U256; +use sp_runtime::traits::Dispatchable; +use sp_std::{marker::PhantomData, vec::Vec}; +use tangle_primitives::services::{Field, OperatorPreferences, ServiceBlueprint}; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod mock_evm; +#[cfg(test)] +mod tests; + +/// Precompile for the `Services` pallet. +pub struct ServicesPrecompile(PhantomData); + +#[precompile_utils::precompile] +impl ServicesPrecompile +where + Runtime: pallet_services::Config + pallet_evm::Config, + Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, + ::RuntimeOrigin: From>, + Runtime::RuntimeCall: From>, +{ + /// Create a new blueprint. + #[precompile::public("createBlueprint(bytes)")] + fn create_blueprint( + handle: &mut impl PrecompileHandle, + blueprint_data: UnboundedBytes, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + + let blueprint_data: Vec = blueprint_data.into(); + let blueprint: ServiceBlueprint = + Decode::decode(&mut &blueprint_data[..]) + .map_err(|_| revert("Invalid blueprint data"))?; + + let call = pallet_services::Call::::create_blueprint { blueprint }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + /// Register as an operator for a specific blueprint. + #[precompile::public("registerOperator(uint256,bytes,bytes)")] + fn register_operator( + handle: &mut impl PrecompileHandle, + blueprint_id: U256, + preferences: UnboundedBytes, + registration_args: UnboundedBytes, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + + let blueprint_id: u64 = blueprint_id.as_u64(); + let preferences: Vec = preferences.into(); + let registration_args: Vec = registration_args.into(); + let preferences: OperatorPreferences = Decode::decode(&mut &preferences[..]) + .map_err(|_| revert("Invalid preferences data"))?; + + let registration_args: Vec> = + Decode::decode(&mut ®istration_args[..]) + .map_err(|_| revert("Invalid registration arguments"))?; + + let call = pallet_services::Call::::register { + blueprint_id, + preferences, + registration_args, + }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + /// Unregister as an operator from a blueprint. + #[precompile::public("unregisterOperator(uint256)")] + fn unregister_operator(handle: &mut impl PrecompileHandle, blueprint_id: U256) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + + let blueprint_id: u64 = blueprint_id.as_u64(); + + let call = pallet_services::Call::::unregister { blueprint_id }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + /// Request a new service. + #[precompile::public("requestService(uint256,bytes,bytes,bytes)")] + fn request_service( + handle: &mut impl PrecompileHandle, + blueprint_id: U256, + permitted_callers_data: UnboundedBytes, + service_providers_data: UnboundedBytes, + request_args_data: UnboundedBytes, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + + let blueprint_id: u64 = blueprint_id.as_u64(); + let permitted_callers_data: Vec = permitted_callers_data.into(); + let service_providers_data: Vec = service_providers_data.into(); + let request_args_data: Vec = request_args_data.into(); + + let permitted_callers: Vec = + Decode::decode(&mut &permitted_callers_data[..]) + .map_err(|_| revert("Invalid permitted callers data"))?; + + let service_providers: Vec = + Decode::decode(&mut &service_providers_data[..]) + .map_err(|_| revert("Invalid service providers data"))?; + + let request_args: Vec> = + Decode::decode(&mut &request_args_data[..]) + .map_err(|_| revert("Invalid request arguments data"))?; + + let call = pallet_services::Call::::request { + blueprint_id, + permitted_callers, + service_providers, + ttl: 10000_u32.into(), + request_args, + }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + /// Terminate a service. + #[precompile::public("terminateService(uint256)")] + fn terminate_service(handle: &mut impl PrecompileHandle, service_id: U256) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + + let service_id: u64 = service_id.as_u64(); + + let call = pallet_services::Call::::terminate { service_id }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + /// Approve a request. + #[precompile::public("approve(uint256)")] + fn approve(handle: &mut impl PrecompileHandle, request_id: U256) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + let request_id: u64 = request_id.as_u64(); + + let call = pallet_services::Call::::approve { request_id }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + /// Reject a service request. + #[precompile::public("reject(uint256)")] + fn reject(handle: &mut impl PrecompileHandle, request_id: U256) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + let request_id: u64 = request_id.as_u64(); + + let call = pallet_services::Call::::reject { request_id }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + /// Terminate a service by the owner of the service. + #[precompile::public("terminate(uint256)")] + fn terminate(handle: &mut impl PrecompileHandle, service_id: U256) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + let service_id: u64 = service_id.as_u64(); + + let call = pallet_services::Call::::terminate { service_id }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + /// Call a job in the service. + #[precompile::public("callJob(uint256,uint8,bytes)")] + fn call_job( + handle: &mut impl PrecompileHandle, + service_id: U256, + job: u8, + args_data: UnboundedBytes, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + let service_id: u64 = service_id.as_u64(); + let args: Vec = args_data.into(); + + let decoded_args: Vec> = + Decode::decode(&mut &args[..]) + .map_err(|_| revert("Invalid job call arguments data"))?; + + let call = pallet_services::Call::::call { service_id, job, args: decoded_args }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + /// Submit the result for a job call. + #[precompile::public("submitResult(uint256,uint256,bytes)")] + fn submit_result( + handle: &mut impl PrecompileHandle, + service_id: U256, + call_id: U256, + result_data: UnboundedBytes, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + let service_id: u64 = service_id.as_u64(); + let call_id: u64 = call_id.as_u64(); + let result: Vec = result_data.into(); + + let decoded_result: Vec> = + Decode::decode(&mut &result[..]).map_err(|_| revert("Invalid job result data"))?; + + let call = pallet_services::Call::::submit_result { + service_id, + call_id, + result: decoded_result, + }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } +} diff --git a/precompiles/services/src/mock.rs b/precompiles/services/src/mock.rs new file mode 100644 index 00000000..c90fe5c4 --- /dev/null +++ b/precompiles/services/src/mock.rs @@ -0,0 +1,595 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Webb Technologies Inc. +// +// Tangle 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. +// +// Tangle 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 Tangle. If not, see . +#![allow(clippy::all)] +use super::*; +use frame_election_provider_support::{ + bounds::{ElectionBounds, ElectionBoundsBuilder}, + onchain, SequentialPhragmen, +}; +use frame_support::pallet_prelude::Hooks; +use frame_support::pallet_prelude::Weight; +use frame_support::PalletId; +use frame_support::{ + construct_runtime, parameter_types, + traits::{ConstU128, ConstU64, Everything, OneSessionHandler}, +}; +use mock_evm::MockedEvmRunner; +use pallet_evm::GasWeightMapping; +use pallet_services::EvmGasWeightMapping; +use pallet_session::historical as pallet_session_historical; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use serde::Deserialize; +use serde::Serialize; +use sp_core::{ + self, sr25519, sr25519::Public as sr25519Public, ConstU32, RuntimeDebug, H160, H256, +}; +use sp_keystore::{testing::MemoryKeystore, KeystoreExt, KeystorePtr}; +use sp_runtime::{ + testing::UintAuthorityId, + traits::{ConvertInto, IdentityLookup}, + AccountId32, BuildStorage, Perbill, +}; +use std::{collections::BTreeMap, sync::Arc}; + +pub type AccountId = AccountId32; +pub type Balance = u128; + +const ALICE: u8 = 1; +const BOB: u8 = 2; +const CHARLIE: u8 = 3; +const DAVE: u8 = 4; +const EVE: u8 = 5; + +impl frame_system::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Block = Block; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type RuntimeTask = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<1>; + type AccountStore = System; + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = (); + type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = (); + type FreezeIdentifier = (); + type MaxFreezes = (); +} + +parameter_types! { + pub ElectionBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::default() + .voters_count(5_000.into()).targets_count(1_250.into()).build(); + pub ElectionBoundsMultiPhase: ElectionBounds = ElectionBoundsBuilder::default() + .voters_count(10_000.into()).targets_count(1_500.into()).build(); +} + +impl pallet_session::historical::Config for Runtime { + type FullIdentification = AccountId; + type FullIdentificationOf = ConvertInto; +} + +sp_runtime::impl_opaque_keys! { + pub struct MockSessionKeys { + pub other: MockSessionHandler, + } +} + +pub struct MockSessionHandler; +impl OneSessionHandler for MockSessionHandler { + type Key = UintAuthorityId; + + fn on_genesis_session<'a, I: 'a>(_: I) + where + I: Iterator, + AccountId: 'a, + { + } + + fn on_new_session<'a, I: 'a>(_: bool, _: I, _: I) + where + I: Iterator, + AccountId: 'a, + { + } + + fn on_disabled(_validator_index: u32) {} +} + +impl sp_runtime::BoundToRuntimeAppPublic for MockSessionHandler { + type Public = UintAuthorityId; +} + +pub struct MockSessionManager; + +impl pallet_session::SessionManager for MockSessionManager { + fn end_session(_: sp_staking::SessionIndex) {} + fn start_session(_: sp_staking::SessionIndex) {} + fn new_session(idx: sp_staking::SessionIndex) -> Option> { + if idx == 0 || idx == 1 || idx == 2 { + Some(vec![mock_pub_key(1), mock_pub_key(2), mock_pub_key(3), mock_pub_key(4)]) + } else { + None + } + } +} + +parameter_types! { + pub const Period: u64 = 1; + pub const Offset: u64 = 0; +} + +impl pallet_session::Config for Runtime { + type SessionManager = MockSessionManager; + type Keys = MockSessionKeys; + type ShouldEndSession = pallet_session::PeriodicSessions; + type NextSessionRotation = pallet_session::PeriodicSessions; + type SessionHandler = (MockSessionHandler,); + type RuntimeEvent = RuntimeEvent; + type ValidatorId = AccountId; + type ValidatorIdOf = pallet_staking::StashOf; + type WeightInfo = (); +} + +pub struct OnChainSeqPhragmen; +impl onchain::Config for OnChainSeqPhragmen { + type System = Runtime; + type Solver = SequentialPhragmen; + type DataProvider = Staking; + type WeightInfo = (); + type MaxWinners = ConstU32<100>; + type Bounds = ElectionBoundsOnChain; +} + +/// Upper limit on the number of NPOS nominations. +const MAX_QUOTA_NOMINATIONS: u32 = 16; + +impl pallet_staking::Config for Runtime { + type Currency = Balances; + type CurrencyBalance = ::Balance; + type UnixTime = pallet_timestamp::Pallet; + type CurrencyToVote = (); + type RewardRemainder = (); + type RuntimeEvent = RuntimeEvent; + type Slash = (); + type Reward = (); + type SessionsPerEra = (); + type SlashDeferDuration = (); + type AdminOrigin = frame_system::EnsureRoot; + type BondingDuration = (); + type SessionInterface = (); + type EraPayout = (); + type NextNewSession = Session; + type MaxExposurePageSize = ConstU32<64>; + type MaxControllersInDeprecationBatch = ConstU32<100>; + type OffendingValidatorsThreshold = (); + type ElectionProvider = onchain::OnChainExecution; + type GenesisElectionProvider = Self::ElectionProvider; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; + type TargetList = pallet_staking::UseValidatorsMap; + type MaxUnlockingChunks = ConstU32<32>; + type HistoryDepth = ConstU32<84>; + type EventListeners = (); + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; + type NominationsQuota = pallet_staking::FixedNominationsQuota; + type WeightInfo = (); +} + +parameter_types! { + pub const ServicesPalletId: PalletId = PalletId(*b"py/srvcs"); +} + +pub struct PalletEVMGasWeightMapping; + +impl EvmGasWeightMapping for PalletEVMGasWeightMapping { + fn gas_to_weight(gas: u64, without_base_weight: bool) -> Weight { + pallet_evm::FixedGasWeightMapping::::gas_to_weight(gas, without_base_weight) + } + + fn weight_to_gas(weight: Weight) -> u64 { + pallet_evm::FixedGasWeightMapping::::weight_to_gas(weight) + } +} + +const PRECOMPILE_ADDRESS_BYTES: [u8; 32] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, +]; + +#[derive( + Eq, + PartialEq, + Ord, + PartialOrd, + Clone, + Encode, + Decode, + Debug, + MaxEncodedLen, + Serialize, + Deserialize, + derive_more::Display, + scale_info::TypeInfo, +)] +pub enum TestAccount { + Empty, + Alex, + Bob, + Dave, + Charlie, + Eve, + PrecompileAddress, +} + +impl Default for TestAccount { + fn default() -> Self { + Self::Empty + } +} + +// needed for associated type in pallet_evm +impl AddressMapping for TestAccount { + fn into_account_id(h160_account: H160) -> AccountId32 { + match h160_account { + a if a == H160::repeat_byte(0x01) => TestAccount::Alex.into(), + a if a == H160::repeat_byte(0x02) => TestAccount::Bob.into(), + a if a == H160::repeat_byte(0x03) => TestAccount::Charlie.into(), + a if a == H160::repeat_byte(0x04) => TestAccount::Dave.into(), + a if a == H160::repeat_byte(0x05) => TestAccount::Eve.into(), + a if a == H160::from_low_u64_be(6) => TestAccount::PrecompileAddress.into(), + _ => TestAccount::Empty.into(), + } + } +} + +impl AddressMapping for TestAccount { + fn into_account_id(h160_account: H160) -> sp_core::sr25519::Public { + match h160_account { + a if a == H160::repeat_byte(0x01) => sr25519Public::from_raw([1u8; 32]), + a if a == H160::repeat_byte(0x02) => sr25519Public::from_raw([2u8; 32]), + a if a == H160::repeat_byte(0x03) => sr25519Public::from_raw([3u8; 32]), + a if a == H160::repeat_byte(0x04) => sr25519Public::from_raw([4u8; 32]), + a if a == H160::repeat_byte(0x05) => sr25519Public::from_raw([5u8; 32]), + a if a == H160::from_low_u64_be(6) => sr25519Public::from_raw(PRECOMPILE_ADDRESS_BYTES), + _ => sr25519Public::from_raw([0u8; 32]), + } + } +} + +impl From for H160 { + fn from(x: TestAccount) -> H160 { + match x { + TestAccount::Alex => H160::repeat_byte(0x01), + TestAccount::Bob => H160::repeat_byte(0x02), + TestAccount::Charlie => H160::repeat_byte(0x03), + TestAccount::Dave => H160::repeat_byte(0x04), + TestAccount::Eve => H160::repeat_byte(0x05), + TestAccount::PrecompileAddress => H160::from_low_u64_be(6), + _ => Default::default(), + } + } +} + +impl From for AccountId32 { + fn from(x: TestAccount) -> Self { + match x { + TestAccount::Alex => AccountId32::from([1u8; 32]), + TestAccount::Bob => AccountId32::from([2u8; 32]), + TestAccount::Charlie => AccountId32::from([3u8; 32]), + TestAccount::Dave => AccountId32::from([4u8; 32]), + TestAccount::Eve => AccountId32::from([5u8; 32]), + TestAccount::PrecompileAddress => AccountId32::from(PRECOMPILE_ADDRESS_BYTES), + _ => AccountId32::from([0u8; 32]), + } + } +} + +impl From for sp_core::sr25519::Public { + fn from(x: TestAccount) -> Self { + match x { + TestAccount::Alex => sr25519Public::from_raw([1u8; 32]), + TestAccount::Bob => sr25519Public::from_raw([2u8; 32]), + TestAccount::Charlie => sr25519Public::from_raw([3u8; 32]), + TestAccount::Dave => sr25519Public::from_raw([4u8; 32]), + TestAccount::Eve => sr25519Public::from_raw([5u8; 32]), + TestAccount::PrecompileAddress => sr25519Public::from_raw(PRECOMPILE_ADDRESS_BYTES), + _ => sr25519Public::from_raw([0u8; 32]), + } + } +} + +pub struct MockDelegationManager; +impl tangle_primitives::traits::MultiAssetDelegationInfo + for MockDelegationManager +{ + type AssetId = u32; + + fn get_current_round() -> tangle_primitives::types::RoundIndex { + Default::default() + } + + fn is_operator(_operator: &AccountId) -> bool { + // dont care + true + } + + fn is_operator_active(operator: &AccountId) -> bool { + if operator == &mock_pub_key(10) { + return false; + } + true + } + + fn get_operator_stake(_operator: &AccountId) -> Balance { + Default::default() + } + + fn get_total_delegation_by_asset_id( + _operator: &AccountId, + _asset_id: &Self::AssetId, + ) -> Balance { + Default::default() + } +} + +parameter_types! { + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxFields: u32 = 256; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxFieldsSize: u32 = 1024; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxMetadataLength: u32 = 1024; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxJobsPerService: u32 = 1024; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxOperatorsPerService: u32 = 1024; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxPermittedCallers: u32 = 256; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxServicesPerOperator: u32 = 1024; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxBlueprintsPerOperator: u32 = 1024; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxServicesPerUser: u32 = 1024; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxBinariesPerGadget: u32 = 64; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxSourcesPerGadget: u32 = 64; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxGitOwnerLength: u32 = 1024; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxGitRepoLength: u32 = 1024; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxGitTagLength: u32 = 1024; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxBinaryNameLength: u32 = 1024; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxIpfsHashLength: u32 = 46; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxContainerRegistryLength: u32 = 1024; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxContainerImageNameLength: u32 = 1024; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxContainerImageTagLength: u32 = 1024; +} + +impl pallet_services::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ForceOrigin = frame_system::EnsureRoot; + type Currency = Balances; + type PalletId = ServicesPalletId; + type EvmRunner = MockedEvmRunner; + type EvmGasWeightMapping = PalletEVMGasWeightMapping; + type MaxFields = MaxFields; + type MaxFieldsSize = MaxFieldsSize; + type MaxMetadataLength = MaxMetadataLength; + type MaxJobsPerService = MaxJobsPerService; + type MaxOperatorsPerService = MaxOperatorsPerService; + type MaxPermittedCallers = MaxPermittedCallers; + type MaxServicesPerOperator = MaxServicesPerOperator; + type MaxBlueprintsPerOperator = MaxBlueprintsPerOperator; + type MaxServicesPerUser = MaxServicesPerUser; + type MaxBinariesPerGadget = MaxBinariesPerGadget; + type MaxSourcesPerGadget = MaxSourcesPerGadget; + type MaxGitOwnerLength = MaxGitOwnerLength; + type MaxGitRepoLength = MaxGitRepoLength; + type MaxGitTagLength = MaxGitTagLength; + type MaxBinaryNameLength = MaxBinaryNameLength; + type MaxIpfsHashLength = MaxIpfsHashLength; + type MaxContainerRegistryLength = MaxContainerRegistryLength; + type MaxContainerImageNameLength = MaxContainerImageNameLength; + type MaxContainerImageTagLength = MaxContainerImageTagLength; + type Constraints = pallet_services::types::ConstraintsOf; + type OperatorDelegationManager = MockDelegationManager; + type WeightInfo = (); +} + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime + { + System: frame_system, + Timestamp: pallet_timestamp, + Balances: pallet_balances, + Services: pallet_services, + EVM: pallet_evm, + Ethereum: pallet_ethereum, + Session: pallet_session, + Staking: pallet_staking, + Historical: pallet_session_historical, + } +); + +pub fn mock_pub_key(id: u8) -> AccountId { + sr25519::Public::from_raw([id; 32]).into() +} + +pub fn mock_authorities(vec: Vec) -> Vec { + vec.into_iter().map(|id| mock_pub_key(id)).collect() +} + +pub const CGGMP21_BLUEPRINT: H160 = H160([0x21; 20]); + +/// Build test externalities, prepopulated with data for testing democracy precompiles +#[derive(Default)] +pub(crate) struct ExtBuilder; + +impl ExtBuilder { + /// Build the test externalities for use in tests + pub(crate) fn build(self) -> sp_io::TestExternalities { + let ids = vec![ALICE, BOB, CHARLIE, DAVE, EVE]; + new_test_ext_raw_authorities(mock_authorities(ids)) + } +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext_raw_authorities(authorities: Vec) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + // We use default for brevity, but you can configure as desired if needed. + let balances: Vec<_> = authorities.iter().map(|i| (i.clone(), 20_000_u128)).collect(); + pallet_balances::GenesisConfig:: { balances } + .assimilate_storage(&mut t) + .unwrap(); + + let stakers: Vec<_> = authorities + .iter() + .map(|authority| { + ( + authority.clone(), + authority.clone(), + 10_000_u128, + pallet_staking::StakerStatus::::Validator, + ) + }) + .collect(); + + let staking_config = pallet_staking::GenesisConfig:: { + stakers, + validator_count: 4, + force_era: pallet_staking::Forcing::ForceNew, + minimum_validator_count: 0, + max_validator_count: Some(5), + max_nominator_count: Some(5), + invulnerables: vec![], + ..Default::default() + }; + + staking_config.assimilate_storage(&mut t).unwrap(); + + let mut evm_accounts = BTreeMap::new(); + + let cggmp21_blueprint_json: serde_json::Value = serde_json::from_str(include_str!( + "../../../pallets/services/src/test-artifacts/CGGMP21Blueprint.json" + )) + .unwrap(); + let cggmp21_blueprint_code = hex::decode( + cggmp21_blueprint_json["deployedBytecode"]["object"] + .as_str() + .unwrap() + .replace("0x", ""), + ) + .unwrap(); + evm_accounts.insert( + CGGMP21_BLUEPRINT, + fp_evm::GenesisAccount { + code: cggmp21_blueprint_code, + storage: Default::default(), + nonce: Default::default(), + balance: Default::default(), + }, + ); + + let evm_config = + pallet_evm::GenesisConfig:: { accounts: evm_accounts, ..Default::default() }; + + evm_config.assimilate_storage(&mut t).unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.register_extension(KeystoreExt(Arc::new(MemoryKeystore::new()) as KeystorePtr)); + ext.execute_with(|| System::set_block_number(1)); + ext.execute_with(|| { + System::set_block_number(1); + Session::on_initialize(1); + >::on_initialize(1); + }); + + ext +} diff --git a/precompiles/services/src/mock_evm.rs b/precompiles/services/src/mock_evm.rs new file mode 100644 index 00000000..901b9a0e --- /dev/null +++ b/precompiles/services/src/mock_evm.rs @@ -0,0 +1,329 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Webb Technologies Inc. +// +// Tangle 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. +// +// Tangle 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 Tangle. If not, see . +#![allow(clippy::all)] +use crate::mock::{ + AccountId, Balances, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, Timestamp, +}; +use crate::ServicesPrecompile; +use crate::ServicesPrecompileCall; +use fp_evm::FeeCalculator; +use frame_support::{ + parameter_types, + traits::{Currency, FindAuthor, OnUnbalanced}, + weights::Weight, + PalletId, +}; +use pallet_ethereum::{EthereumBlockHashMapping, IntermediateStateRoot, PostLogContent, RawOrigin}; +use pallet_evm::{EnsureAddressNever, EnsureAddressOrigin, OnChargeEVMTransaction}; +use precompile_utils::precompile_set::{AddressU64, PrecompileAt, PrecompileSetBuilder}; +use sp_core::{keccak_256, ConstU32, H160, H256, U256}; +use sp_runtime::{ + traits::{DispatchInfoOf, Dispatchable}, + transaction_validity::{TransactionValidity, TransactionValidityError}, + ConsensusEngineId, +}; + +pub type Precompiles = + PrecompileSetBuilder, ServicesPrecompile>,)>; + +pub type PCall = ServicesPrecompileCall; + +parameter_types! { + pub const MinimumPeriod: u64 = 6000 / 2; +} + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +pub struct FixedGasPrice; +impl FeeCalculator for FixedGasPrice { + fn min_gas_price() -> (U256, Weight) { + (1.into(), Weight::zero()) + } +} + +pub struct EnsureAddressAlways; +impl EnsureAddressOrigin for EnsureAddressAlways { + type Success = (); + + fn try_address_origin( + _address: &H160, + _origin: OuterOrigin, + ) -> Result { + Ok(()) + } + + fn ensure_address_origin( + _address: &H160, + _origin: OuterOrigin, + ) -> Result { + Ok(()) + } +} + +pub struct FindAuthorTruncated; +impl FindAuthor for FindAuthorTruncated { + fn find_author<'a, I>(_digests: I) -> Option + where + I: 'a + IntoIterator, + { + Some(address_build(0).address) + } +} + +const BLOCK_GAS_LIMIT: u64 = 150_000_000; +const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; + +parameter_types! { + pub const TransactionByteFee: u64 = 1; + pub const ChainId: u64 = 42; + pub const EVMModuleId: PalletId = PalletId(*b"py/evmpa"); + pub PrecompilesValue: Precompiles = Precompiles::new(); + pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT); + pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); + pub const WeightPerGas: Weight = Weight::from_parts(20_000, 0); +} + +parameter_types! { + pub SuicideQuickClearLimit: u32 = 0; +} + +pub struct DealWithFees; +impl OnUnbalanced for DealWithFees { + fn on_unbalanceds(_fees_then_tips: impl Iterator) { + // whatever + } +} +pub struct FreeEVMExecution; + +impl OnChargeEVMTransaction for FreeEVMExecution { + type LiquidityInfo = (); + + fn withdraw_fee( + _who: &H160, + _fee: U256, + ) -> Result> { + Ok(()) + } + + fn correct_and_deposit_fee( + _who: &H160, + _corrected_fee: U256, + _base_fee: U256, + already_withdrawn: Self::LiquidityInfo, + ) -> Self::LiquidityInfo { + already_withdrawn + } + + fn pay_priority_fee(_tip: Self::LiquidityInfo) {} +} + +/// Type alias for negative imbalance during fees +type RuntimeNegativeImbalance = + ::AccountId>>::NegativeImbalance; + +/// See: [`pallet_evm::EVMCurrencyAdapter`] +pub struct CustomEVMCurrencyAdapter; + +impl OnChargeEVMTransaction for CustomEVMCurrencyAdapter { + type LiquidityInfo = Option; + + fn withdraw_fee( + who: &H160, + fee: U256, + ) -> Result> { + let pallet_services_address = pallet_services::Pallet::::address(); + // Make pallet services account free to use + if who == &pallet_services_address { + return Ok(None); + } + // fallback to the default implementation + as OnChargeEVMTransaction< + Runtime, + >>::withdraw_fee(who, fee) + } + + fn correct_and_deposit_fee( + who: &H160, + corrected_fee: U256, + base_fee: U256, + already_withdrawn: Self::LiquidityInfo, + ) -> Self::LiquidityInfo { + let pallet_services_address = pallet_services::Pallet::::address(); + // Make pallet services account free to use + if who == &pallet_services_address { + return already_withdrawn; + } + // fallback to the default implementation + as OnChargeEVMTransaction< + Runtime, + >>::correct_and_deposit_fee(who, corrected_fee, base_fee, already_withdrawn) + } + + fn pay_priority_fee(tip: Self::LiquidityInfo) { + as OnChargeEVMTransaction< + Runtime, + >>::pay_priority_fee(tip) + } +} + +impl pallet_evm::Config for Runtime { + type FeeCalculator = FixedGasPrice; + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; + type WeightPerGas = WeightPerGas; + type BlockHashMapping = EthereumBlockHashMapping; + type CallOrigin = EnsureAddressAlways; + type WithdrawOrigin = EnsureAddressNever; + type AddressMapping = crate::mock::TestAccount; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type PrecompilesType = Precompiles; + type PrecompilesValue = PrecompilesValue; + type ChainId = ChainId; + type BlockGasLimit = BlockGasLimit; + type Runner = pallet_evm::runner::stack::Runner; + type OnChargeTransaction = CustomEVMCurrencyAdapter; + type OnCreate = (); + type SuicideQuickClearLimit = SuicideQuickClearLimit; + type FindAuthor = FindAuthorTruncated; + type GasLimitPovSizeRatio = GasLimitPovSizeRatio; + type Timestamp = Timestamp; + type WeightInfo = (); +} + +parameter_types! { + pub const PostBlockAndTxnHashes: PostLogContent = PostLogContent::BlockAndTxnHashes; +} + +impl pallet_ethereum::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type StateRoot = IntermediateStateRoot; + type PostLogContent = PostBlockAndTxnHashes; + type ExtraDataLength = ConstU32<30>; +} + +impl fp_self_contained::SelfContainedCall for RuntimeCall { + type SignedInfo = H160; + + fn is_self_contained(&self) -> bool { + match self { + RuntimeCall::Ethereum(call) => call.is_self_contained(), + _ => false, + } + } + + fn check_self_contained(&self) -> Option> { + match self { + RuntimeCall::Ethereum(call) => call.check_self_contained(), + _ => None, + } + } + + fn validate_self_contained( + &self, + info: &Self::SignedInfo, + dispatch_info: &DispatchInfoOf, + len: usize, + ) -> Option { + match self { + RuntimeCall::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len), + _ => None, + } + } + + fn pre_dispatch_self_contained( + &self, + info: &Self::SignedInfo, + dispatch_info: &DispatchInfoOf, + len: usize, + ) -> Option> { + match self { + RuntimeCall::Ethereum(call) => { + call.pre_dispatch_self_contained(info, dispatch_info, len) + }, + _ => None, + } + } + + fn apply_self_contained( + self, + info: Self::SignedInfo, + ) -> Option>> { + match self { + call @ RuntimeCall::Ethereum(pallet_ethereum::Call::transact { .. }) => { + Some(call.dispatch(RuntimeOrigin::from(RawOrigin::EthereumTransaction(info)))) + }, + _ => None, + } + } +} + +pub struct MockedEvmRunner; + +impl pallet_services::EvmRunner for MockedEvmRunner { + type Error = pallet_evm::Error; + + fn call( + source: sp_core::H160, + target: sp_core::H160, + input: Vec, + value: sp_core::U256, + gas_limit: u64, + is_transactional: bool, + validate: bool, + ) -> Result> { + let max_fee_per_gas = FixedGasPrice::min_gas_price().0; + let max_priority_fee_per_gas = max_fee_per_gas.saturating_mul(U256::from(2)); + let nonce = None; + let access_list = Default::default(); + let weight_limit = None; + let proof_size_base_cost = None; + <::Runner as pallet_evm::Runner>::call( + source, + target, + input, + value, + gas_limit, + Some(max_fee_per_gas), + Some(max_priority_fee_per_gas), + nonce, + access_list, + is_transactional, + validate, + weight_limit, + proof_size_base_cost, + ::config(), + ) + .map_err(|o| pallet_services::traits::RunnerError { error: o.error, weight: o.weight }) + } +} + +pub struct AccountInfo { + pub address: H160, +} + +pub fn address_build(seed: u8) -> AccountInfo { + let private_key = H256::from_slice(&[(seed + 1); 32]); //H256::from_low_u64_be((i + 1) as u64); + let secret_key = libsecp256k1::SecretKey::parse_slice(&private_key[..]).unwrap(); + let public_key = &libsecp256k1::PublicKey::from_secret_key(&secret_key).serialize()[1..65]; + let address = H160::from(H256::from(keccak_256(public_key))); + + AccountInfo { address } +} diff --git a/precompiles/services/src/tests.rs b/precompiles/services/src/tests.rs new file mode 100644 index 00000000..59314e2b --- /dev/null +++ b/precompiles/services/src/tests.rs @@ -0,0 +1,320 @@ +use crate::mock::*; +use crate::mock_evm::PCall; +use crate::mock_evm::PrecompilesValue; +use pallet_services::types::ConstraintsOf; +use pallet_services::Instances; +use pallet_services::Operators; +use pallet_services::OperatorsProfile; +use parity_scale_codec::Encode; +use precompile_utils::prelude::UnboundedBytes; +use precompile_utils::testing::*; +use sp_core::ecdsa; +use sp_core::{H160, U256}; +use sp_runtime::bounded_vec; +use sp_runtime::AccountId32; +use tangle_primitives::services::FieldType; +use tangle_primitives::services::JobDefinition; +use tangle_primitives::services::JobMetadata; +use tangle_primitives::services::JobResultVerifier; +use tangle_primitives::services::PriceTargets; +use tangle_primitives::services::ServiceMetadata; +use tangle_primitives::services::ServiceRegistrationHook; +use tangle_primitives::services::ServiceRequestHook; +use tangle_primitives::services::{ApprovalPrefrence, OperatorPreferences, ServiceBlueprint}; + +fn zero_key() -> ecdsa::Public { + ecdsa::Public([0; 33]) +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum MachineKind { + Large, + Medium, + Small, +} + +/// All prices are specified in USD/hr (in u64, so 1e6 = 1$) +fn price_targets(kind: MachineKind) -> PriceTargets { + match kind { + MachineKind::Large => PriceTargets { + cpu: 2_000, + mem: 1_000, + storage_hdd: 100, + storage_ssd: 200, + storage_nvme: 300, + }, + MachineKind::Medium => PriceTargets { + cpu: 1_000, + mem: 500, + storage_hdd: 50, + storage_ssd: 100, + storage_nvme: 150, + }, + MachineKind::Small => { + PriceTargets { cpu: 500, mem: 250, storage_hdd: 25, storage_ssd: 50, storage_nvme: 75 } + }, + } +} + +fn cggmp21_blueprint() -> ServiceBlueprint> { + ServiceBlueprint { + metadata: ServiceMetadata { name: "CGGMP21 TSS".try_into().unwrap(), ..Default::default() }, + jobs: bounded_vec![ + JobDefinition { + metadata: JobMetadata { name: "keygen".try_into().unwrap(), ..Default::default() }, + params: bounded_vec![FieldType::Uint8], + result: bounded_vec![FieldType::Bytes], + verifier: JobResultVerifier::Evm(CGGMP21_BLUEPRINT), + }, + JobDefinition { + metadata: JobMetadata { name: "sign".try_into().unwrap(), ..Default::default() }, + params: bounded_vec![FieldType::Uint64, FieldType::Bytes], + result: bounded_vec![FieldType::Bytes], + verifier: JobResultVerifier::Evm(CGGMP21_BLUEPRINT), + }, + ], + registration_hook: ServiceRegistrationHook::Evm(CGGMP21_BLUEPRINT), + registration_params: bounded_vec![], + request_hook: ServiceRequestHook::Evm(CGGMP21_BLUEPRINT), + request_params: bounded_vec![], + gadget: Default::default(), + } +} +#[test] +fn test_create_blueprint() { + ExtBuilder.build().execute_with(|| { + // Create blueprint + let blueprint_data = cggmp21_blueprint(); + + PrecompilesValue::get() + .prepare_test( + TestAccount::Alex, + H160::from_low_u64_be(1), + PCall::create_blueprint { + blueprint_data: UnboundedBytes::from(blueprint_data.encode()), + }, + ) + .execute_returns(()); + + // Ensure the blueprint was created successfully + assert_eq!(Services::next_blueprint_id(), 1); + }); +} + +#[test] +fn test_register_operator() { + ExtBuilder.build().execute_with(|| { + // First create the blueprint + let blueprint_data = cggmp21_blueprint(); + + PrecompilesValue::get() + .prepare_test( + TestAccount::Alex, + H160::from_low_u64_be(1), + PCall::create_blueprint { + blueprint_data: UnboundedBytes::from(blueprint_data.encode()), + }, + ) + .execute_returns(()); + + // Now register operator + let preferences_data = OperatorPreferences { + key: zero_key(), + approval: ApprovalPrefrence::default(), + price_targets: price_targets(MachineKind::Large), + } + .encode(); + + PrecompilesValue::get() + .prepare_test( + TestAccount::Bob, + H160::from_low_u64_be(1), + PCall::register_operator { + blueprint_id: U256::from(0), // We use the first blueprint + preferences: UnboundedBytes::from(preferences_data), + registration_args: UnboundedBytes::from(vec![0u8]), + }, + ) + .execute_returns(()); + + // Check that the operator profile exists + let account: AccountId32 = TestAccount::Bob.into(); + assert!(OperatorsProfile::::get(account).is_ok()); + }); +} + +#[test] +fn test_request_service() { + ExtBuilder.build().execute_with(|| { + // First create the blueprint + let blueprint_data = cggmp21_blueprint(); + + PrecompilesValue::get() + .prepare_test( + TestAccount::Alex, + H160::from_low_u64_be(1), + PCall::create_blueprint { + blueprint_data: UnboundedBytes::from(blueprint_data.encode()), + }, + ) + .execute_returns(()); + + // Now register operator + let preferences_data = OperatorPreferences { + key: zero_key(), + approval: ApprovalPrefrence::default(), + price_targets: price_targets(MachineKind::Large), + } + .encode(); + + PrecompilesValue::get() + .prepare_test( + TestAccount::Bob, + H160::from_low_u64_be(1), + PCall::register_operator { + blueprint_id: U256::from(0), + preferences: UnboundedBytes::from(preferences_data), + registration_args: UnboundedBytes::from(vec![0u8]), + }, + ) + .execute_returns(()); + + // Finally, request the service + let permitted_callers_data: Vec = vec![TestAccount::Alex.into()]; + let service_providers_data: Vec = vec![TestAccount::Bob.into()]; + let request_args_data = vec![0u8]; + + PrecompilesValue::get() + .prepare_test( + TestAccount::Alex, + H160::from_low_u64_be(1), + PCall::request_service { + blueprint_id: U256::from(0), // Use the first blueprint + permitted_callers_data: UnboundedBytes::from(permitted_callers_data.encode()), + service_providers_data: UnboundedBytes::from(service_providers_data.encode()), + request_args_data: UnboundedBytes::from(request_args_data), + }, + ) + .execute_returns(()); + + // Ensure the service instance is created + assert!(Instances::::contains_key(0)); + }); +} + +#[test] +fn test_unregister_operator() { + ExtBuilder.build().execute_with(|| { + // First register operator (after blueprint creation) + let blueprint_data = cggmp21_blueprint(); + + PrecompilesValue::get() + .prepare_test( + TestAccount::Alex, + H160::from_low_u64_be(1), + PCall::create_blueprint { + blueprint_data: UnboundedBytes::from(blueprint_data.encode()), + }, + ) + .execute_returns(()); + + let preferences_data = OperatorPreferences { + key: zero_key(), + approval: ApprovalPrefrence::default(), + price_targets: price_targets(MachineKind::Large), + } + .encode(); + + PrecompilesValue::get() + .prepare_test( + TestAccount::Bob, + H160::from_low_u64_be(1), + PCall::register_operator { + blueprint_id: U256::from(0), + preferences: UnboundedBytes::from(preferences_data), + registration_args: UnboundedBytes::from(vec![0u8]), + }, + ) + .execute_returns(()); + + // Now unregister operator + PrecompilesValue::get() + .prepare_test( + TestAccount::Bob, + H160::from_low_u64_be(1), + PCall::unregister_operator { blueprint_id: U256::from(0) }, + ) + .execute_returns(()); + + // Ensure the operator is removed + let bob_account: AccountId32 = TestAccount::Bob.into(); + assert!(!Operators::::contains_key(0, bob_account)); + }); +} + +#[test] +fn test_terminate_service() { + ExtBuilder.build().execute_with(|| { + // First request a service + let blueprint_data = cggmp21_blueprint(); + + PrecompilesValue::get() + .prepare_test( + TestAccount::Alex, + H160::from_low_u64_be(1), + PCall::create_blueprint { + blueprint_data: UnboundedBytes::from(blueprint_data.encode()), + }, + ) + .execute_returns(()); + + let preferences_data = OperatorPreferences { + key: zero_key(), + approval: ApprovalPrefrence::default(), + price_targets: price_targets(MachineKind::Large), + } + .encode(); + + PrecompilesValue::get() + .prepare_test( + TestAccount::Bob, + H160::from_low_u64_be(1), + PCall::register_operator { + blueprint_id: U256::from(0), + preferences: UnboundedBytes::from(preferences_data), + registration_args: UnboundedBytes::from(vec![0u8]), + }, + ) + .execute_returns(()); + + let permitted_callers_data: Vec = vec![TestAccount::Alex.into()]; + let service_providers_data: Vec = vec![TestAccount::Bob.into()]; + let request_args_data = vec![0u8]; + + PrecompilesValue::get() + .prepare_test( + TestAccount::Alex, + H160::from_low_u64_be(1), + PCall::request_service { + blueprint_id: U256::from(0), + permitted_callers_data: UnboundedBytes::from(permitted_callers_data.encode()), + service_providers_data: UnboundedBytes::from(service_providers_data.encode()), + request_args_data: UnboundedBytes::from(request_args_data), + }, + ) + .execute_returns(()); + + // Now terminate the service + PrecompilesValue::get() + .prepare_test( + TestAccount::Alex, + H160::from_low_u64_be(1), + PCall::terminate_service { service_id: U256::from(0) }, + ) + .execute_returns(()); + + // Ensure the service is removed + assert!(!Instances::::contains_key(0)); + }); +} diff --git a/precompiles/tangle-lst/Cargo.toml b/precompiles/tangle-lst/Cargo.toml index f3baace8..148e8a7b 100644 --- a/precompiles/tangle-lst/Cargo.toml +++ b/precompiles/tangle-lst/Cargo.toml @@ -23,7 +23,7 @@ sp-std = { workspace = true } # Frontier fp-evm = { workspace = true } pallet-evm = { workspace = true, features = ["forbid-evm-reentrancy"] } - +pallet-tangle-lst = { workspace = true } tangle-primitives = { workspace = true } [dev-dependencies] @@ -57,5 +57,6 @@ std = [ "sp-runtime/std", "sp-std/std", "tangle-primitives/std", - "pallet-assets/std" + "pallet-assets/std", + "pallet-tangle-lst/std", ] diff --git a/precompiles/tangle-lst/src/lib.rs b/precompiles/tangle-lst/src/lib.rs index 13040864..c5f8de63 100644 --- a/precompiles/tangle-lst/src/lib.rs +++ b/precompiles/tangle-lst/src/lib.rs @@ -53,7 +53,7 @@ type BalanceOf = ::AccountId, >>::Balance; -use pallet_tangle_lst::{PoolId, PoolState, BondExtra}; +use pallet_tangle_lst::{BondExtra, PoolId, PoolState}; use sp_runtime::Perbill; pub struct TangleLstPrecompile(PhantomData); @@ -61,218 +61,282 @@ pub struct TangleLstPrecompile(PhantomData); #[precompile_utils::precompile] impl TangleLstPrecompile where - Runtime: pallet_tangle_lst::Config + pallet_evm::Config, - Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, - ::RuntimeOrigin: From>, - Runtime::RuntimeCall: From>, - BalanceOf: TryFrom + Into + solidity::Codec, - Runtime::AccountId: From, + Runtime: pallet_tangle_lst::Config + pallet_evm::Config, + Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, + ::RuntimeOrigin: From>, + Runtime::RuntimeCall: From>, + BalanceOf: TryFrom + Into + solidity::Codec, + Runtime::AccountId: From, { - #[precompile::public("join(uint256,uint256)")] - fn join(handle: &mut impl PrecompileHandle, amount: U256, pool_id: U256) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - - let amount: BalanceOf = amount.try_into().map_err(|_| revert("Invalid amount"))?; - let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; - - let call = pallet_tangle_lst::Call::::join { amount, pool_id }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(()) - } - - #[precompile::public("bondExtra(uint256,uint8,uint256)")] - fn bond_extra(handle: &mut impl PrecompileHandle, pool_id: U256, extra_type: u8, extra: U256) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - - let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; - let extra: BalanceOf = extra.try_into().map_err(|_| revert("Invalid extra amount"))?; - - let extra = match extra_type { - 0 => BondExtra::FreeBalance(extra), - 1 => BondExtra::Rewards, - _ => return Err(revert("Invalid extra type")), - }; - - let call = pallet_tangle_lst::Call::::bond_extra { pool_id, extra }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(()) - } - - #[precompile::public("unbond(bytes32,uint256,uint256)")] - fn unbond(handle: &mut impl PrecompileHandle, member_account: H256, pool_id: U256, unbonding_points: U256) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - - let member_account = Self::convert_to_account_id(member_account)?; - let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; - let unbonding_points: BalanceOf = unbonding_points.try_into().map_err(|_| revert("Invalid unbonding points"))?; - - let call = pallet_tangle_lst::Call::::unbond { member_account, pool_id, unbonding_points }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(()) - } - - #[precompile::public("poolWithdrawUnbonded(uint256,uint32)")] - fn pool_withdraw_unbonded(handle: &mut impl PrecompileHandle, pool_id: U256, num_slashing_spans: u32) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - - let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; - - let call = pallet_tangle_lst::Call::::pool_withdraw_unbonded { pool_id, num_slashing_spans }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(()) - } - - #[precompile::public("withdrawUnbonded(bytes32,uint256,uint32)")] - fn withdraw_unbonded(handle: &mut impl PrecompileHandle, member_account: H256, pool_id: U256, num_slashing_spans: u32) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - - let member_account = Self::convert_to_account_id(member_account)?; - let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; - - let call = pallet_tangle_lst::Call::::withdraw_unbonded { member_account, pool_id, num_slashing_spans }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(()) - } - - #[precompile::public("create(uint256,bytes32,bytes32,bytes32)")] - fn create(handle: &mut impl PrecompileHandle, amount: U256, root: H256, nominator: H256, bouncer: H256) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - - let amount: BalanceOf = amount.try_into().map_err(|_| revert("Invalid amount"))?; - let root = Self::convert_to_account_id(root)?; - let nominator = Self::convert_to_account_id(nominator)?; - let bouncer = Self::convert_to_account_id(bouncer)?; - - let call = pallet_tangle_lst::Call::::create { amount, root, nominator, bouncer }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(()) - } - - #[precompile::public("createWithPoolId(uint256,bytes32,bytes32,bytes32,uint256)")] - fn create_with_pool_id(handle: &mut impl PrecompileHandle, amount: U256, root: H256, nominator: H256, bouncer: H256, pool_id: U256) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - - let amount: BalanceOf = amount.try_into().map_err(|_| revert("Invalid amount"))?; - let root = Self::convert_to_account_id(root)?; - let nominator = Self::convert_to_account_id(nominator)?; - let bouncer = Self::convert_to_account_id(bouncer)?; - let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; - - let call = pallet_tangle_lst::Call::::create_with_pool_id { amount, root, nominator, bouncer, pool_id }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(()) - } - - #[precompile::public("nominate(uint256,bytes32[])")] - fn nominate(handle: &mut impl PrecompileHandle, pool_id: U256, validators: Vec) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - - let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; - let validators: Vec = validators.into_iter().map(Self::convert_to_account_id).collect::>()?; - - let call = pallet_tangle_lst::Call::::nominate { pool_id, validators }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(()) - } - - #[precompile::public("setState(uint256,uint8)")] - fn set_state(handle: &mut impl PrecompileHandle, pool_id: U256, state: u8) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - - let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; - let state = match state { - 0 => PoolState::Open, - 1 => PoolState::Blocked, - 2 => PoolState::Destroying, - _ => return Err(revert("Invalid state")), - }; - - let call = pallet_tangle_lst::Call::::set_state { pool_id, state }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(()) - } - - #[precompile::public("setMetadata(uint256,bytes)")] - fn set_metadata(handle: &mut impl PrecompileHandle, pool_id: U256, metadata: Vec) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - - let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; - - let call = pallet_tangle_lst::Call::::set_metadata { pool_id, metadata }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(()) - } - - #[precompile::public("setConfigs(uint256,uint256,uint32,uint32)")] - fn set_configs( - handle: &mut impl PrecompileHandle, - min_join_bond: U256, - min_create_bond: U256, - max_pools: u32, - global_max_commission: u32, - ) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - ensure_root(handle)?; - - let min_join_bond: Option> = if min_join_bond == U256::zero() { - None - } else { - Some(min_join_bond.try_into().map_err(|_| revert("Invalid min join bond"))?) - }; - - let min_create_bond: Option> = if min_create_bond == U256::zero() { - None - } else { - Some(min_create_bond.try_into().map_err(|_| revert("Invalid min create bond"))?) - }; - - let max_pools = if max_pools == 0 { None } else { Some(max_pools) }; - - let global_max_commission = if global_max_commission == 0 { - None - } else { - Some(Perbill::from_percent(global_max_commission)) - }; - - let call = pallet_tangle_lst::Call::::set_configs { - min_join_bond: min_join_bond.map(ConfigOp::Set).unwrap_or(ConfigOp::Noop), - min_create_bond: min_create_bond.map(ConfigOp::Set).unwrap_or(ConfigOp::Noop), - max_pools: max_pools.map(ConfigOp::Set).unwrap_or(ConfigOp::Noop), - global_max_commission: global_max_commission.map(ConfigOp::Set).unwrap_or(ConfigOp::Noop), - }; - - RuntimeHelper::::try_dispatch(handle, RawOrigin::Root.into(), call)?; - - Ok(()) - } + #[precompile::public("join(uint256,uint256)")] + fn join(handle: &mut impl PrecompileHandle, amount: U256, pool_id: U256) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + + let amount: BalanceOf = amount.try_into().map_err(|_| revert("Invalid amount"))?; + let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; + + let call = pallet_tangle_lst::Call::::join { amount, pool_id }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + #[precompile::public("bondExtra(uint256,uint8,uint256)")] + fn bond_extra( + handle: &mut impl PrecompileHandle, + pool_id: U256, + extra_type: u8, + extra: U256, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + + let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; + let extra: BalanceOf = + extra.try_into().map_err(|_| revert("Invalid extra amount"))?; + + let extra = match extra_type { + 0 => BondExtra::FreeBalance(extra), + 1 => BondExtra::Rewards, + _ => return Err(revert("Invalid extra type")), + }; + + let call = pallet_tangle_lst::Call::::bond_extra { pool_id, extra }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + #[precompile::public("unbond(bytes32,uint256,uint256)")] + fn unbond( + handle: &mut impl PrecompileHandle, + member_account: H256, + pool_id: U256, + unbonding_points: U256, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + + let member_account = Self::convert_to_account_id(member_account)?; + let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; + let unbonding_points: BalanceOf = + unbonding_points.try_into().map_err(|_| revert("Invalid unbonding points"))?; + + let call = pallet_tangle_lst::Call::::unbond { + member_account, + pool_id, + unbonding_points, + }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + #[precompile::public("poolWithdrawUnbonded(uint256,uint32)")] + fn pool_withdraw_unbonded( + handle: &mut impl PrecompileHandle, + pool_id: U256, + num_slashing_spans: u32, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + + let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; + + let call = pallet_tangle_lst::Call::::pool_withdraw_unbonded { + pool_id, + num_slashing_spans, + }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + #[precompile::public("withdrawUnbonded(bytes32,uint256,uint32)")] + fn withdraw_unbonded( + handle: &mut impl PrecompileHandle, + member_account: H256, + pool_id: U256, + num_slashing_spans: u32, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + + let member_account = Self::convert_to_account_id(member_account)?; + let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; + + let call = pallet_tangle_lst::Call::::withdraw_unbonded { + member_account, + pool_id, + num_slashing_spans, + }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + #[precompile::public("create(uint256,bytes32,bytes32,bytes32)")] + fn create( + handle: &mut impl PrecompileHandle, + amount: U256, + root: H256, + nominator: H256, + bouncer: H256, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + + let amount: BalanceOf = amount.try_into().map_err(|_| revert("Invalid amount"))?; + let root = Self::convert_to_account_id(root)?; + let nominator = Self::convert_to_account_id(nominator)?; + let bouncer = Self::convert_to_account_id(bouncer)?; + + let call = pallet_tangle_lst::Call::::create { amount, root, nominator, bouncer }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + #[precompile::public("createWithPoolId(uint256,bytes32,bytes32,bytes32,uint256)")] + fn create_with_pool_id( + handle: &mut impl PrecompileHandle, + amount: U256, + root: H256, + nominator: H256, + bouncer: H256, + pool_id: U256, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + + let amount: BalanceOf = amount.try_into().map_err(|_| revert("Invalid amount"))?; + let root = Self::convert_to_account_id(root)?; + let nominator = Self::convert_to_account_id(nominator)?; + let bouncer = Self::convert_to_account_id(bouncer)?; + let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; + + let call = pallet_tangle_lst::Call::::create_with_pool_id { + amount, + root, + nominator, + bouncer, + pool_id, + }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + #[precompile::public("nominate(uint256,bytes32[])")] + fn nominate( + handle: &mut impl PrecompileHandle, + pool_id: U256, + validators: Vec, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + + let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; + let validators: Vec = validators + .into_iter() + .map(Self::convert_to_account_id) + .collect::>()?; + + let call = pallet_tangle_lst::Call::::nominate { pool_id, validators }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + #[precompile::public("setState(uint256,uint8)")] + fn set_state(handle: &mut impl PrecompileHandle, pool_id: U256, state: u8) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + + let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; + let state = match state { + 0 => PoolState::Open, + 1 => PoolState::Blocked, + 2 => PoolState::Destroying, + _ => return Err(revert("Invalid state")), + }; + + let call = pallet_tangle_lst::Call::::set_state { pool_id, state }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + #[precompile::public("setMetadata(uint256,bytes)")] + fn set_metadata( + handle: &mut impl PrecompileHandle, + pool_id: U256, + metadata: Vec, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); + + let pool_id: PoolId = pool_id.try_into().map_err(|_| revert("Invalid pool id"))?; + + let call = pallet_tangle_lst::Call::::set_metadata { pool_id, metadata }; + + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + Ok(()) + } + + #[precompile::public("setConfigs(uint256,uint256,uint32,uint32)")] + fn set_configs( + handle: &mut impl PrecompileHandle, + min_join_bond: U256, + min_create_bond: U256, + max_pools: u32, + global_max_commission: u32, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + ensure_root(handle)?; + + let min_join_bond: Option> = if min_join_bond == U256::zero() { + None + } else { + Some(min_join_bond.try_into().map_err(|_| revert("Invalid min join bond"))?) + }; + + let min_create_bond: Option> = if min_create_bond == U256::zero() { + None + } else { + Some(min_create_bond.try_into().map_err(|_| revert("Invalid min create bond"))?) + }; + + let max_pools = if max_pools == 0 { None } else { Some(max_pools) }; + + let global_max_commission = if global_max_commission == 0 { + None + } else { + Some(Perbill::from_percent(global_max_commission)) + }; + + let call = pallet_tangle_lst::Call::::set_configs { + min_join_bond: min_join_bond.map(ConfigOp::Set).unwrap_or(ConfigOp::Noop), + min_create_bond: min_create_bond.map(ConfigOp::Set).unwrap_or(ConfigOp::Noop), + max_pools: max_pools.map(ConfigOp::Set).unwrap_or(ConfigOp::Noop), + global_max_commission: global_max_commission + .map(ConfigOp::Set) + .unwrap_or(ConfigOp::Noop), + }; + + RuntimeHelper::::try_dispatch(handle, RawOrigin::Root.into(), call)?; + + Ok(()) + } } diff --git a/runtime/testnet/Cargo.toml b/runtime/testnet/Cargo.toml index b7ac06c5..10735819 100644 --- a/runtime/testnet/Cargo.toml +++ b/runtime/testnet/Cargo.toml @@ -87,6 +87,7 @@ pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-utility = { workspace = true } pallet-multisig = { workspace = true } pallet-vesting = { workspace = true } +pallet-tangle-lst = { workspace = true } # Tangle dependencies pallet-airdrop-claims = { workspace = true } @@ -95,7 +96,6 @@ pallet-services-rpc-runtime-api = { workspace = true } tangle-primitives = { workspace = true, features = ["verifying"] } tangle-crypto-primitives = { workspace = true } pallet-multi-asset-delegation = { workspace = true } -pallet-tangle-lst = { workspace = true } # Frontier dependencies fp-evm = { workspace = true } @@ -135,6 +135,7 @@ pallet-evm-precompile-verify-ecdsa-stark-signature = { workspace = true } pallet-evm-precompile-verify-schnorr-signatures = { workspace = true } pallet-evm-precompile-verify-bls381-signature = { workspace = true } pallet-evm-precompile-multi-asset-delegation = { workspace = true } +pallet-evm-precompile-services = { workspace = true } precompile-utils = { workspace = true } evm-tracer = { workspace = true } @@ -300,7 +301,7 @@ std = [ "pallet-evm-precompile-verify-ecdsa-stark-signature/std", "pallet-evm-precompile-verify-schnorr-signatures/std", "pallet-evm-precompile-verify-bls381-signature/std", - + "pallet-evm-precompile-services/std", # Sygma "sygma-basic-feehandler/std", diff --git a/runtime/testnet/src/precompiles.rs b/runtime/testnet/src/precompiles.rs index 40d01a44..9853530f 100644 --- a/runtime/testnet/src/precompiles.rs +++ b/runtime/testnet/src/precompiles.rs @@ -25,6 +25,7 @@ use pallet_evm_precompile_modexp::Modexp; use pallet_evm_precompile_multi_asset_delegation::MultiAssetDelegationPrecompile; use pallet_evm_precompile_preimage::PreimagePrecompile; use pallet_evm_precompile_registry::PrecompileRegistry; +use pallet_evm_precompile_services::ServicesPrecompile; use pallet_evm_precompile_sha3fips::Sha3FIPS256; use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; use pallet_evm_precompile_staking::StakingPrecompile; @@ -211,8 +212,12 @@ pub type TanglePrecompilesAt = ( MultiAssetDelegationPrecompile, (CallableByContract, CallableByPrecompile), >, + PrecompileAt< + AddressU64<2083>, + ServicesPrecompile, + (CallableByContract, CallableByPrecompile), + >, ); - pub type TanglePrecompiles = PrecompileSetBuilder< R, (