diff --git a/Cargo.lock b/Cargo.lock index bd513d4afba..5f080df16e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3769,6 +3769,16 @@ dependencies = [ "scale-info", ] +[[package]] +name = "demo-proxy-broker" +version = "0.1.0" +dependencies = [ + "gbuiltin-proxy", + "gear-wasm-builder", + "gstd", + "parity-scale-codec", +] + [[package]] name = "demo-proxy-relay" version = "0.1.0" @@ -6213,6 +6223,15 @@ dependencies = [ "scale-info", ] +[[package]] +name = "gbuiltin-proxy" +version = "1.6.2" +dependencies = [ + "derive_more 0.99.18", + "gprimitives", + "scale-info", +] + [[package]] name = "gbuiltin-staking" version = "1.6.2" @@ -11485,6 +11504,7 @@ dependencies = [ "ark-scale 0.0.12", "ark-serialize 0.4.2", "ark-std 0.4.0", + "demo-proxy-broker", "demo-staking-broker", "demo-waiting-proxy", "derive_more 0.99.18", @@ -11496,6 +11516,7 @@ dependencies = [ "frame-support-test", "frame-system", "gbuiltin-bls381", + "gbuiltin-proxy", "gbuiltin-staking", "gear-common", "gear-core", @@ -11514,6 +11535,7 @@ dependencies = [ "pallet-gear-messenger", "pallet-gear-program", "pallet-gear-scheduler", + "pallet-proxy", "pallet-session", "pallet-staking", "pallet-timestamp", @@ -18846,6 +18868,7 @@ dependencies = [ "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", + "gbuiltin-proxy", "gbuiltin-staking", "gear-common", "gear-core", diff --git a/Cargo.toml b/Cargo.toml index da0cb93b1f8..d8ed67f243c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,7 @@ members = [ "examples/program-factory", "examples/program-generator", "examples/proxy", + "examples/proxy-broker", "examples/proxy-relay", "examples/proxy-reservation-with-gas", "examples/read-big-state", @@ -216,6 +217,7 @@ galloc = { path = "galloc" } gbuiltin-bls381 = { path = "gbuiltins/bls381", default-features = false } gbuiltin-eth-bridge = { path = "gbuiltins/eth-bridge", default-features = false } gbuiltin-staking = { path = "gbuiltins/staking" } +gbuiltin-proxy = { path = "gbuiltins/proxy" } gcore = { path = "gcore" } gcli = { path = "gcli" } gclient = { path = "gclient" } @@ -484,6 +486,7 @@ demo-rwlock = { path = "examples/rwlock" } demo-send-from-reservation = { path = "examples/send-from-reservation" } demo-signal-entry = { path = "examples/signal-entry", default-features = false } demo-staking-broker = { path = "examples/staking-broker" } +demo-proxy-broker = { path = "examples/proxy-broker" } demo-state-rollback = { path = "examples/state-rollback" } demo-sync-duplicate = { path = "examples/sync-duplicate" } demo-vec = { path = "examples/vec" } diff --git a/examples/proxy-broker/Cargo.toml b/examples/proxy-broker/Cargo.toml new file mode 100644 index 00000000000..d0d1a943a99 --- /dev/null +++ b/examples/proxy-broker/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "demo-proxy-broker" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +gbuiltin-proxy.workspace = true +gstd.workspace = true +parity-scale-codec.workspace = true + +[build-dependencies] +gear-wasm-builder.workspace = true + +[features] +debug = ["gstd/debug"] +std = [] +default = ["std"] diff --git a/examples/proxy-broker/build.rs b/examples/proxy-broker/build.rs new file mode 100644 index 00000000000..6a370b53a71 --- /dev/null +++ b/examples/proxy-broker/build.rs @@ -0,0 +1,21 @@ +// This file is part of Gear. + +// Copyright (C) 2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +fn main() { + gear_wasm_builder::build(); +} diff --git a/examples/proxy-broker/src/lib.rs b/examples/proxy-broker/src/lib.rs new file mode 100644 index 00000000000..28b7cf0f8c3 --- /dev/null +++ b/examples/proxy-broker/src/lib.rs @@ -0,0 +1,33 @@ +// This file is part of Gear. +// +// Copyright (C) 2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// +// This program 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. +// +// This program 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 this program. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "std")] +mod code { + include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +} + +#[cfg(feature = "std")] +pub use code::WASM_BINARY_OPT as WASM_BINARY; + +#[cfg(not(feature = "std"))] +pub const WASM_BINARY: &[u8] = &[]; + +#[cfg(not(feature = "std"))] +pub mod wasm; diff --git a/examples/proxy-broker/src/wasm.rs b/examples/proxy-broker/src/wasm.rs new file mode 100644 index 00000000000..f3f9225360e --- /dev/null +++ b/examples/proxy-broker/src/wasm.rs @@ -0,0 +1,72 @@ +// This file is part of Gear. +// +// Copyright (C) 2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// +// This program 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. +// +// This program 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 this program. If not, see . + +//! Basic implementation of the proxy-broker for demo purpose only. + +use gbuiltin_proxy::Request; +use gstd::{actor_id, debug, errors::Error, msg, ActorId}; + +// Proxy builtin actor program id (hardcoded for all runtimes); +// +// Calculated as hash((b"built/in", 3u64).encode()) +const BUILTIN_ADDRESS: ActorId = + actor_id!("0xf2816ced0b15749595392d3a18b5a2363d6fefe5b3b6153739f218151b7acdbf"); + +#[gstd::async_main] +async fn main() { + let request: Request = msg::load().expect("handle: invalid payload received"); + match request { + add_proxy @ Request::AddProxy { .. } => { + debug!( + "handle: Sending `add_proxy` request with data {:?}", + add_proxy + ); + + send_request(add_proxy).await; + } + remove_proxy @ Request::RemoveProxy { .. } => { + debug!( + "handle: Sending `remove_proxy` request with data {:?}", + remove_proxy + ); + + send_request(remove_proxy).await; + } + } +} + +async fn send_request(req: Request) { + let res = msg::send_for_reply(BUILTIN_ADDRESS, req, 0, 0) + .expect("handle::send_request: failed sending message for reply") + .await; + match res { + Ok(_) => { + debug!("handle::send_request: Success reply from builtin actor received"); + msg::reply_bytes(b"Success", 0).expect("Failed to send reply"); + } + Err(e) => { + debug!("handle::send_request: Error reply from builtin actor received: {e:?}"); + match e { + Error::ErrorReply(payload, _) => { + panic!("{}", payload); + } + _ => panic!("Error in upstream program"), + } + } + } +} diff --git a/gbuiltins/proxy/Cargo.toml b/gbuiltins/proxy/Cargo.toml new file mode 100644 index 00000000000..4912223625b --- /dev/null +++ b/gbuiltins/proxy/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "gbuiltin-proxy" +description = "Types and traits to interact with proxy builtin actor." +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +gprimitives.workspace = true +derive_more.workspace = true +scale-info = { workspace = true, features = ["derive"] } diff --git a/gbuiltins/proxy/src/lib.rs b/gbuiltins/proxy/src/lib.rs new file mode 100644 index 00000000000..49eecad834b --- /dev/null +++ b/gbuiltins/proxy/src/lib.rs @@ -0,0 +1,64 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Types used to communicate with proxy built-in. + +#![no_std] + +use gprimitives::ActorId; +use scale_info::scale::{self, Decode, Encode}; + +/// Request that can be handled by the proxy builtin. +/// +/// Currently all proxies aren't required to send announcement, +/// i.e. no delays for the delegate actions. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Encode, Decode)] +#[codec(crate = scale)] +pub enum Request { + /// Add proxy request. + /// + /// Requests to add `delegate` as a delegate for the actions + /// defined by `proxy_type` to be done on behalf of the request + /// sender. + AddProxy { + delegate: ActorId, + proxy_type: ProxyType, + }, + /// Remove proxy request. + /// + /// Request sender asks to remove `delegate` with set of allowed actions + /// defined in `proxy_type` from his list of proxies. + RemoveProxy { + delegate: ActorId, + proxy_type: ProxyType, + }, +} + +/// Proxy type. +/// +/// The mirror enum for the one defined in vara-runtime crate. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Encode, Decode)] +#[codec(crate = scale)] +pub enum ProxyType { + Any, + NonTransfer, + Governance, + Staking, + IdentityJudgement, + CancelProxy, +} diff --git a/pallets/gear-builtin/Cargo.toml b/pallets/gear-builtin/Cargo.toml index cbb9c77edf4..4430f848c20 100644 --- a/pallets/gear-builtin/Cargo.toml +++ b/pallets/gear-builtin/Cargo.toml @@ -30,6 +30,7 @@ common.workspace = true core-processor.workspace = true gbuiltin-bls381.workspace = true gbuiltin-staking.workspace = true +gbuiltin-proxy.workspace = true gear-core.workspace = true gear-core-errors.workspace = true gear-runtime-interface.workspace = true @@ -41,10 +42,12 @@ sp-std.workspace = true sp-runtime = { workspace = true, features = ["serde"] } pallet-gear.workspace = true pallet-staking.workspace = true +pallet-proxy.workspace = true [dev-dependencies] demo-waiting-proxy.workspace = true demo-staking-broker.workspace = true +demo-proxy-broker.workspace = true gprimitives.workspace = true sp-core = { workspace = true, features = ["std"] } sp-externalities = { workspace = true, features = ["std"] } @@ -81,6 +84,7 @@ std = [ "gear-runtime-interface/std", "pallet-gear/std", "pallet-staking/std", + "pallet-proxy/std", "parity-scale-codec/std", "scale-info/std", "sp-crypto-ec-utils/std", diff --git a/pallets/gear-builtin/src/lib.rs b/pallets/gear-builtin/src/lib.rs index e31069a873e..4109567c7ad 100644 --- a/pallets/gear-builtin/src/lib.rs +++ b/pallets/gear-builtin/src/lib.rs @@ -36,6 +36,7 @@ extern crate alloc; pub mod benchmarking; pub mod bls12_381; +pub mod proxy; pub mod staking; pub mod weights; @@ -58,6 +59,7 @@ use core_processor::{ process_execution_error, process_success, SuccessfulDispatchResultKind, SystemReservationContext, }; +use frame_support::dispatch::extract_actual_weight; use gear_core::{ gas::GasCounter, ids::{hash, ProgramId}, @@ -73,6 +75,8 @@ use sp_std::prelude::*; pub use pallet::*; +type CallOf = ::RuntimeCall; + const LOG_TARGET: &str = "gear::builtin"; /// Built-in actor error type @@ -172,6 +176,7 @@ impl BuiltinCollection for Tuple { #[frame_support::pallet] pub mod pallet { use super::*; + use common::Origin; use frame_support::{ dispatch::{GetDispatchInfo, PostDispatchInfo}, pallet_prelude::*, @@ -212,6 +217,45 @@ pub mod pallet { pub fn generate_actor_id(builtin_id: u64) -> ProgramId { hash((SEED, builtin_id).encode().as_slice()).into() } + + pub(crate) fn dispatch_call( + origin: ProgramId, + call: CallOf, + gas_limit: u64, + ) -> (Result<(), BuiltinActorError>, u64) + where + T::AccountId: Origin, + { + let call_info = call.get_dispatch_info(); + + // Necessary upfront gas sufficiency check + if gas_limit < call_info.weight.ref_time() { + return (Err(BuiltinActorError::InsufficientGas), 0_u64); + } + + // Execute call + let res = call.dispatch(frame_system::RawOrigin::Signed(origin.cast()).into()); + let actual_gas = extract_actual_weight(&res, &call_info).ref_time(); + + match res { + Ok(_post_info) => { + log::debug!( + target: LOG_TARGET, + "Call dispatched successfully", + ); + (Ok(()), actual_gas) + } + Err(e) => { + log::debug!(target: LOG_TARGET, "Error dispatching call: {:?}", e); + ( + Err(BuiltinActorError::Custom(LimitedStr::from_small_str( + e.into(), + ))), + actual_gas, + ) + } + } + } } } diff --git a/pallets/gear-builtin/src/mock.rs b/pallets/gear-builtin/src/mock.rs index 98ce908b8b7..77d22b0809b 100644 --- a/pallets/gear-builtin/src/mock.rs +++ b/pallets/gear-builtin/src/mock.rs @@ -16,23 +16,27 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{self as pallet_gear_builtin, bls12_381, ActorWithId, BuiltinActor, BuiltinActorError}; +use crate::{ + self as pallet_gear_builtin, bls12_381, proxy, ActorWithId, BuiltinActor, BuiltinActorError, +}; use common::{GasProvider, GasTree}; use core::cell::RefCell; use frame_support::{ construct_runtime, parameter_types, - traits::{ConstBool, ConstU32, ConstU64, FindAuthor, OnFinalize, OnInitialize}, + traits::{ConstBool, ConstU32, ConstU64, FindAuthor, InstanceFilter, OnFinalize, OnInitialize}, }; use frame_support_test::TestRandomness; use frame_system::{self as system, pallet_prelude::BlockNumberFor}; +use gbuiltin_proxy::ProxyType as BuiltinProxyType; use gear_core::{ ids::ProgramId, message::{Payload, StoredDispatch}, }; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use sp_core::H256; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, Perbill, Permill, + BuildStorage, Perbill, Permill, RuntimeDebug, }; use sp_std::convert::{TryFrom, TryInto}; @@ -79,6 +83,7 @@ construct_runtime!( Authorship: pallet_authorship, Timestamp: pallet_timestamp, Staking: pallet_staking, + Proxy: pallet_proxy, GearProgram: pallet_gear_program, GearMessenger: pallet_gear_messenger, GearScheduler: pallet_gear_scheduler, @@ -94,12 +99,105 @@ parameter_types! { pub const ExistentialDeposit: Balance = EXISTENTIAL_DEPOSIT; } +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + RuntimeDebug, + MaxEncodedLen, + scale_info::TypeInfo, +)] +pub enum ProxyType { + Any, + NonTransfer, + Governance, + Staking, + IdentityJudgement, + CancelProxy, +} + +impl Default for ProxyType { + fn default() -> Self { + Self::Any + } +} + +impl From for ProxyType { + fn from(proxy_type: BuiltinProxyType) -> Self { + match proxy_type { + BuiltinProxyType::Any => ProxyType::Any, + BuiltinProxyType::NonTransfer => ProxyType::NonTransfer, + BuiltinProxyType::Governance => ProxyType::Governance, + BuiltinProxyType::Staking => ProxyType::Staking, + BuiltinProxyType::IdentityJudgement => ProxyType::IdentityJudgement, + BuiltinProxyType::CancelProxy => ProxyType::CancelProxy, + } + } +} + +impl InstanceFilter for ProxyType { + fn filter(&self, c: &RuntimeCall) -> bool { + match self { + ProxyType::Any => true, + ProxyType::NonTransfer => !matches!(c, RuntimeCall::Balances(..)), + ProxyType::CancelProxy => { + matches!( + c, + RuntimeCall::Proxy(pallet_proxy::Call::reject_announcement { .. }) + ) + } + ProxyType::Staking => matches!(c, RuntimeCall::Staking(..)), + ProxyType::Governance | ProxyType::IdentityJudgement => { + unimplemented!("No pallets defined in test runtime") + } + } + } + fn is_superset(&self, o: &Self) -> bool { + match (self, o) { + (x, y) if x == y => true, + (ProxyType::Any, _) => true, + (_, ProxyType::Any) => false, + (ProxyType::NonTransfer, _) => true, + _ => false, + } + } +} + common::impl_pallet_system!(Test); common::impl_pallet_balances!(Test); common::impl_pallet_authorship!(Test); common::impl_pallet_timestamp!(Test); common::impl_pallet_staking!(Test); +parameter_types! { + pub const ProxyDepositBase: Balance = 1; + pub const ProxyDepositFactor: Balance = 1; + pub const MaxProxies: u32 = 100; + pub const MaxPending: u32 = 100; + pub const AnnouncementDepositBase: Balance = 1; + pub const AnnouncementDepositFactor: Balance = 1; +} + +impl pallet_proxy::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = ProxyType; + type ProxyDepositBase = ProxyDepositBase; + type ProxyDepositFactor = ProxyDepositFactor; + type MaxProxies = MaxProxies; + type WeightInfo = (); + type MaxPending = MaxPending; + type CallHasher = BlakeTwo256; + type AnnouncementDepositBase = AnnouncementDepositBase; + type AnnouncementDepositFactor = AnnouncementDepositBase; +} + parameter_types! { pub const BlockGasLimit: u64 = 100_000_000_000; pub const OutgoingLimit: u32 = 1024; @@ -222,6 +320,7 @@ impl pallet_gear_builtin::Config for Test { ActorWithId, ActorWithId, ActorWithId<1, bls12_381::Actor>, + ActorWithId<3, proxy::Actor>, ); type WeightInfo = (); } diff --git a/pallets/gear-builtin/src/proxy.rs b/pallets/gear-builtin/src/proxy.rs new file mode 100644 index 00000000000..0a68ca2c5c2 --- /dev/null +++ b/pallets/gear-builtin/src/proxy.rs @@ -0,0 +1,95 @@ +// This file is part of Gear. + +// Copyright (C) 2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Proxy builtin actor implementation. + +use super::*; +use common::Origin; +use gbuiltin_proxy::{ProxyType as BuiltinProxyType, Request}; +use pallet_proxy::Config as ProxyConfig; +use sp_runtime::traits::StaticLookup; + +/// Proxy builtin actor. +pub struct Actor(PhantomData); + +impl Actor +where + T::AccountId: Origin, + ::ProxyType: From, + CallOf: From>, +{ + /// Casts received request to a runtime call. + fn cast(request: Request) -> Result, BuiltinActorError> { + Ok(match request { + Request::AddProxy { + delegate, + proxy_type, + } => { + let delegate = T::Lookup::unlookup(delegate.cast()); + let proxy_type = proxy_type.into(); + let delay = 0u32.into(); + + pallet_proxy::Call::::add_proxy { + delegate, + proxy_type, + delay, + } + } + Request::RemoveProxy { + delegate, + proxy_type, + } => { + let delegate = T::Lookup::unlookup(delegate.cast()); + let proxy_type = proxy_type.into(); + let delay = 0u32.into(); + + pallet_proxy::Call::::remove_proxy { + delegate, + proxy_type, + delay, + } + } + } + .into()) + } +} + +impl BuiltinActor for Actor +where + T::AccountId: Origin, + ::ProxyType: From, + CallOf: From>, +{ + type Error = BuiltinActorError; + + fn handle(dispatch: &StoredDispatch, gas_limit: u64) -> (Result, u64) { + let Ok(request) = Request::decode(&mut dispatch.payload_bytes()) else { + return (Err(BuiltinActorError::DecodingError), 0); + }; + + let origin = dispatch.source(); + + match Self::cast(request) { + Ok(call) => { + let (result, actual_gas) = Pallet::::dispatch_call(origin, call, gas_limit); + (result.map(|_| Default::default()), actual_gas) + } + Err(e) => (Err(e), gas_limit), + } + } +} diff --git a/pallets/gear-builtin/src/staking.rs b/pallets/gear-builtin/src/staking.rs index d6900bb5f68..a5a24370cd5 100644 --- a/pallets/gear-builtin/src/staking.rs +++ b/pallets/gear-builtin/src/staking.rs @@ -21,13 +21,10 @@ use super::*; use common::Origin; use core::marker::PhantomData; -use frame_support::dispatch::{extract_actual_weight, GetDispatchInfo}; use gbuiltin_staking::*; use pallet_staking::{Config as StakingConfig, NominationsQuota, RewardDestination}; use parity_scale_codec::Decode; -use sp_runtime::traits::{Dispatchable, StaticLookup, UniqueSaturatedInto}; - -type CallOf = ::RuntimeCall; +use sp_runtime::traits::{StaticLookup, UniqueSaturatedInto}; pub struct Actor(PhantomData); @@ -36,41 +33,6 @@ where T::AccountId: Origin, CallOf: From>, { - fn dispatch_call( - origin: T::AccountId, - call: CallOf, - gas_limit: u64, - ) -> (Result<(), BuiltinActorError>, u64) { - let call_info = call.get_dispatch_info(); - - // Necessary upfront gas sufficiency check - if gas_limit < call_info.weight.ref_time() { - return (Err(BuiltinActorError::InsufficientGas), 0_u64); - } - - // Execute call - let res = call.dispatch(frame_system::RawOrigin::Signed(origin).into()); - let actual_gas = extract_actual_weight(&res, &call_info).ref_time(); - match res { - Ok(_post_info) => { - log::debug!( - target: LOG_TARGET, - "Call dispatched successfully", - ); - (Ok(()), actual_gas) - } - Err(e) => { - log::debug!(target: LOG_TARGET, "Error dispatching call: {:?}", e); - ( - Err(BuiltinActorError::Custom(LimitedStr::from_small_str( - e.into(), - ))), - actual_gas, - ) - } - } - } - fn cast(request: Request) -> CallOf { match request { Request::Bond { value, payee } => { @@ -146,7 +108,7 @@ where fn handle(dispatch: &StoredDispatch, gas_limit: u64) -> (Result, u64) { let message = dispatch.message(); - let origin: T::AccountId = dispatch.source().cast(); + let origin = dispatch.source(); let mut payload = message.payload_bytes(); // Rule out payloads that exceed the largest reasonable size. @@ -169,7 +131,7 @@ where // Handle staking requests let call = Self::cast(request); - let (result, gas_spent) = Self::dispatch_call(origin, call, gas_limit); + let (result, gas_spent) = Pallet::::dispatch_call(origin, call, gas_limit); (result.map(|_| Default::default()), gas_spent) } diff --git a/pallets/gear-builtin/src/tests/mod.rs b/pallets/gear-builtin/src/tests/mod.rs index d071cfd12b6..99ab57e66a4 100644 --- a/pallets/gear-builtin/src/tests/mod.rs +++ b/pallets/gear-builtin/src/tests/mod.rs @@ -18,7 +18,8 @@ //! Builtin actor pallet tests. -pub mod bad_builtin_ids; -pub mod basic; -pub mod bls381; -pub mod staking; +mod bad_builtin_ids; +mod basic; +mod bls381; +mod proxy; +mod staking; diff --git a/pallets/gear-builtin/src/tests/proxy.rs b/pallets/gear-builtin/src/tests/proxy.rs new file mode 100644 index 00000000000..d693d9b902c --- /dev/null +++ b/pallets/gear-builtin/src/tests/proxy.rs @@ -0,0 +1,160 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! Proxy builtin tests. + +use super::basic::init_logger; +use crate::mock::*; +use common::Origin; +use demo_proxy_broker::WASM_BINARY; +use frame_support::{assert_err, assert_ok}; +use gbuiltin_proxy::{ProxyType, Request}; +use gear_core::ids::{prelude::*, CodeId, ProgramId}; +use pallet_balances::Call as BalancesCall; +use pallet_proxy::{Error as ProxyError, Event as ProxyEvent}; +use parity_scale_codec::Encode; + +#[test] +fn add_remove_proxy_works() { + init_logger(); + new_test_ext().execute_with(|| { + let proxy_pid = utils::upload_and_initialize_broker(); + + // Add proxy + let add_proxy_req = Request::AddProxy { + delegate: SIGNER.cast(), + proxy_type: ProxyType::Any, + }; + utils::send_proxy_request(proxy_pid, add_proxy_req); + + System::assert_has_event(RuntimeEvent::Proxy(ProxyEvent::ProxyAdded { + delegator: proxy_pid.cast(), + delegatee: SIGNER, + proxy_type: ProxyType::Any.into(), + delay: 0, + })); + + System::reset_events(); + + // Remove proxy + let remove_proxy_req = Request::RemoveProxy { + delegate: SIGNER.cast(), + proxy_type: ProxyType::Any, + }; + utils::send_proxy_request(proxy_pid, remove_proxy_req); + + System::assert_has_event(RuntimeEvent::Proxy(ProxyEvent::ProxyRemoved { + delegator: proxy_pid.cast(), + delegatee: SIGNER, + proxy_type: ProxyType::Any.into(), + delay: 0, + })); + + // Execute proxy + let dest = 42; + let value = EXISTENTIAL_DEPOSIT * 3; + let call = RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest, value }); + + assert_err!( + Proxy::proxy( + RuntimeOrigin::signed(SIGNER), + proxy_pid.cast(), + None, + Box::new(call) + ), + ProxyError::::NotProxy, + ); + }) +} + +#[test] +fn add_execute_proxy_works() { + init_logger(); + new_test_ext().execute_with(|| { + let proxy_pid = utils::upload_and_initialize_broker(); + + // Add proxy + let add_proxy_req = Request::AddProxy { + delegate: SIGNER.cast(), + proxy_type: ProxyType::Any, + }; + utils::send_proxy_request(proxy_pid, add_proxy_req); + + System::assert_has_event(RuntimeEvent::Proxy(ProxyEvent::ProxyAdded { + delegator: proxy_pid.cast(), + delegatee: SIGNER, + proxy_type: ProxyType::Any.into(), + delay: 0, + })); + + // Execute proxy + let dest = 42; + let value = EXISTENTIAL_DEPOSIT * 3; + let call = RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest, value }); + + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(SIGNER), + proxy_pid.cast(), + None, + Box::new(call) + )); + assert_eq!(Balances::free_balance(dest), value); + }) +} + +mod utils { + use super::*; + + pub(super) fn upload_and_initialize_broker() -> ProgramId { + let code = WASM_BINARY; + let salt = b"proxy_broker"; + let pid = ProgramId::generate_from_user(CodeId::generate(code), salt); + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(SIGNER), + code.to_vec(), + salt.to_vec(), + Default::default(), + 10_000_000_000, + 0, + false, + )); + run_to_next_block(); + + // Top-up balance of the proxy so it can pay adding proxy deposit + assert_ok!(>::transfer( + &1, + &pid.cast(), + 10 * EXISTENTIAL_DEPOSIT, + frame_support::traits::ExistenceRequirement::AllowDeath + )); + + pid + } + + pub(super) fn send_proxy_request(proxy_pid: ProgramId, req: Request) { + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(SIGNER), + proxy_pid, + req.encode(), + 10_000_000_000, + 0, + false, + )); + run_to_next_block(); + } +} diff --git a/runtime/vara/Cargo.toml b/runtime/vara/Cargo.toml index 512d3bc73c0..fc902592b4c 100644 --- a/runtime/vara/Cargo.toml +++ b/runtime/vara/Cargo.toml @@ -106,6 +106,7 @@ pallet-gear-builtin-rpc-runtime-api.workspace = true pallet-gear-eth-bridge-rpc-runtime-api.workspace = true runtime-primitives.workspace = true gbuiltin-staking.workspace = true +gbuiltin-proxy.workspace = true [dev-dependencies] sp-io.workspace = true diff --git a/runtime/vara/src/lib.rs b/runtime/vara/src/lib.rs index 1ffec3ed121..0d950b7a5f8 100644 --- a/runtime/vara/src/lib.rs +++ b/runtime/vara/src/lib.rs @@ -65,6 +65,7 @@ use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, }; +use gbuiltin_proxy::ProxyType as BuiltinProxyType; use pallet_election_provider_multi_phase::{GeometricDepositBase, SolutionAccuracyOf}; pub use pallet_gear::manager::{ExtManager, HandleKind}; use pallet_gear_builtin::ActorWithId; @@ -1003,6 +1004,19 @@ impl Default for ProxyType { } } +impl From for ProxyType { + fn from(proxy_type: BuiltinProxyType) -> Self { + match proxy_type { + BuiltinProxyType::Any => ProxyType::Any, + BuiltinProxyType::NonTransfer => ProxyType::NonTransfer, + BuiltinProxyType::Governance => ProxyType::Governance, + BuiltinProxyType::Staking => ProxyType::Staking, + BuiltinProxyType::IdentityJudgement => ProxyType::IdentityJudgement, + BuiltinProxyType::CancelProxy => ProxyType::CancelProxy, + } + } +} + impl InstanceFilter for ProxyType { fn filter(&self, c: &RuntimeCall) -> bool { match self { @@ -1180,6 +1194,8 @@ impl pallet_gear_messenger::Config for Runtime { pub type BuiltinActors = ( ActorWithId<1, pallet_gear_builtin::bls12_381::Actor>, ActorWithId<2, pallet_gear_builtin::staking::Actor>, + // The ID = 3 is for the pallet_gear_eth_bridge::Actor. + ActorWithId<4, pallet_gear_builtin::proxy::Actor>, ); /// Builtin actors arranged in a tuple. @@ -1188,6 +1204,7 @@ pub type BuiltinActors = ( ActorWithId<1, pallet_gear_builtin::bls12_381::Actor>, ActorWithId<2, pallet_gear_builtin::staking::Actor>, ActorWithId<3, pallet_gear_eth_bridge::Actor>, + ActorWithId<4, pallet_gear_builtin::proxy::Actor>, ); impl pallet_gear_builtin::Config for Runtime {