diff --git a/common/src/lib.rs b/common/src/lib.rs index a98a957ad8..28a33c2ae6 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -162,6 +162,33 @@ pub enum ProxyType { RootClaim, } +impl TryFrom for ProxyType { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::Any), + 1 => Ok(Self::Owner), + 2 => Ok(Self::NonCritical), + 3 => Ok(Self::NonTransfer), + 4 => Ok(Self::Senate), + 5 => Ok(Self::NonFungible), + 6 => Ok(Self::Triumvirate), + 7 => Ok(Self::Governance), + 8 => Ok(Self::Staking), + 9 => Ok(Self::Registration), + 10 => Ok(Self::Transfer), + 11 => Ok(Self::SmallTransfer), + 12 => Ok(Self::RootWeights), + 13 => Ok(Self::ChildKeys), + 14 => Ok(Self::SudoUncheckedSetCode), + 15 => Ok(Self::SwapHotkey), + 16 => Ok(Self::SubnetLeaseBeneficiary), + _ => Err(()), + } + } +} + impl Default for ProxyType { // allow all Calls; required to be most permissive fn default() -> Self { diff --git a/evm-tests/src/address-utils.ts b/evm-tests/src/address-utils.ts index ed3abc5008..753eed2530 100644 --- a/evm-tests/src/address-utils.ts +++ b/evm-tests/src/address-utils.ts @@ -1,6 +1,6 @@ import { Address } from "viem" import { encodeAddress } from "@polkadot/util-crypto"; -import { ss58Address } from "@polkadot-labs/hdkd-helpers"; +import { ss58Address, ss58Decode } from "@polkadot-labs/hdkd-helpers"; import { hexToU8a } from "@polkadot/util"; import { blake2AsU8a, decodeAddress } from "@polkadot/util-crypto"; import { Binary } from "polkadot-api"; diff --git a/evm-tests/src/contracts/proxy.ts b/evm-tests/src/contracts/proxy.ts new file mode 100644 index 0000000000..48ffd0a50f --- /dev/null +++ b/evm-tests/src/contracts/proxy.ts @@ -0,0 +1,148 @@ +export const IPROXY_ADDRESS = "0x000000000000000000000000000000000000080b"; + +export const IProxyABI = [ + { + "inputs": [ + { + "internalType": "uint8", + "name": "proxy_type", + "type": "uint8" + }, + { + "internalType": "uint32", + "name": "delay", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "index", + "type": "uint16" + } + ], + "name": "createPureProxy", + "outputs": [ + { + "internalType": "bytes32", + "name": "proxy", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "spawner", + "type": "bytes32" + }, + { + "internalType": "uint8", + "name": "proxy_type", + "type": "uint8" + }, + { + "internalType": "uint16", + "name": "index", + "type": "uint16" + }, + { + "internalType": "uint32", + "name": "height", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "ext_index", + "type": "uint32" + } + ], + "name": "killPureProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "real", + "type": "bytes32" + }, + { + "internalType": "uint8[]", + "name": "force_proxy_type", // optional + "type": "uint8[]" + }, + { + "internalType": "uint8[]", + "name": "call", + "type": "uint8[]" + } + ], + "name": "proxyCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "removeProxies", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { + "inputs": [], + "name": "pokeDeposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "delegate", + "type": "bytes32" + }, + { + "internalType": "uint8", + "name": "proxy_type", + "type": "uint8" + }, + { + "internalType": "uint32", + "name": "delay", + "type": "uint32" + } + ], + "name": "removeProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "delegate", + "type": "bytes32" + }, + { + "internalType": "uint8", + "name": "proxy_type", + "type": "uint8" + }, + { + "internalType": "uint32", + "name": "delay", + "type": "uint32" + } + ], + "name": "addProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +]; diff --git a/evm-tests/test/pure-proxy.precompile.test.ts b/evm-tests/test/pure-proxy.precompile.test.ts new file mode 100644 index 0000000000..1a34b02cf7 --- /dev/null +++ b/evm-tests/test/pure-proxy.precompile.test.ts @@ -0,0 +1,163 @@ +import * as assert from "assert"; + +import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" +import { generateRandomEthersWallet } from "../src/utils"; +import { devnet, MultiAddress } from "@polkadot-api/descriptors" +import { PolkadotSigner, TypedApi } from "polkadot-api"; +import { convertH160ToPublicKey, convertH160ToSS58, convertPublicKeyToSs58 } from "../src/address-utils" +import { IProxyABI, IPROXY_ADDRESS } from "../src/contracts/proxy" +import { ethers } from 'ethers'; +import { forceSetBalanceToEthAddress, forceSetBalanceToSs58Address } from "../src/subtensor"; +import { KeyPair } from "@polkadot-labs/hdkd-helpers"; + +import { decodeAddress } from "@polkadot/util-crypto"; + +async function getTransferCallCode(api: TypedApi, receiver: KeyPair, transferAmount: number) { + + const unsignedTx = api.tx.Balances.transfer_keep_alive({ + dest: MultiAddress.Id(convertPublicKeyToSs58(receiver.publicKey)), + value: BigInt(1000000000), + }); + const encodedCallDataBytes = await unsignedTx.getEncodedData(); + + // encoded call should be 0x050300d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d02286bee + // const transferCall = encodedCallDataBytes + + const data = encodedCallDataBytes.asBytes() + + return [...data] +} + +async function getProxies(api: TypedApi, address: string) { + const entries = await api.query.Proxy.Proxies.getEntries() + const result = [] + for (const entry of entries) { + const proxyAddress = entry.keyArgs[0] + const values = entry.value + const proxies = values[0] + for (const proxy of proxies) { + if (proxy.delegate === address) { + result.push(proxyAddress) + } + } + } + return result +} + +describe("Test pure proxy precompile", () => { + const evmWallet = generateRandomEthersWallet(); + const evmWallet2 = generateRandomEthersWallet(); + const evmWallet3 = generateRandomEthersWallet(); + const receiver = getRandomSubstrateKeypair(); + + let api: TypedApi + + let alice: PolkadotSigner; + + before(async () => { + api = await getDevnetApi() + alice = await getAliceSigner(); + + await forceSetBalanceToEthAddress(api, evmWallet.address) + await forceSetBalanceToEthAddress(api, evmWallet2.address) + await forceSetBalanceToEthAddress(api, evmWallet3.address) + }) + + it("Call createPureProxy, then use proxy to call transfer", async () => { + const proxies = await getProxies(api, convertH160ToSS58(evmWallet.address)) + const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet) + console.log("evmWallet", evmWallet.address) + + const type = 0; + const delay = 0; + const index = 0; + const tx = await contract.createPureProxy(type, delay, index) + const response = await tx.wait() + console.log("response", response.blockNumber) + + const proxiesAfterAdd = await getProxies(api, convertH160ToSS58(evmWallet.address)) + + const length = proxiesAfterAdd.length + assert.equal(length, proxies.length + 1, "proxy should be set") + const proxy = proxiesAfterAdd[proxiesAfterAdd.length - 1] + + await forceSetBalanceToSs58Address(api, proxy) + const balance = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free + + const amount = 1000000000; + + const callCode = await getTransferCallCode(api, receiver, amount) + const tx2 = await contract.proxyCall(decodeAddress(proxy), [type], callCode) + await tx2.wait() + + const balanceAfter = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free + assert.equal(balanceAfter, balance + BigInt(amount), "balance should be increased") + }) + + it("Call createPureProxy, add multiple proxies", async () => { + const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet) + const type = 0; + const delay = 0; + const index = 0; + const proxies = await getProxies(api, convertH160ToSS58(evmWallet.address)) + const length = proxies.length + for (let i = 0; i < 5; i++) { + const tx = await contract.createPureProxy(type, delay, index) + await tx.wait() + + await new Promise(resolve => setTimeout(resolve, 500)); + const currentProxies = await getProxies(api, convertH160ToSS58(evmWallet.address)) + assert.equal(currentProxies.length, length + i + 1, "proxy should be set") + } + }) + + it("Call createPureProxy, edge cases, call via wrong proxy", async () => { + const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet2) + const amount = 1000000000; + const callCode = await getTransferCallCode(api, receiver, amount) + const type = 0; + + // call with wrong proxy + try { + const tx = await contract.proxyCall(receiver, [type], callCode) + await tx.wait() + } catch (error) { + assert.notEqual(error, undefined, "should fail if proxy not set") + } + }) + + it("Call createProxy, then use proxy to call transfer", async () => { + const proxies = await api.query.Proxy.Proxies.getValue(convertH160ToSS58(evmWallet2.address)) + const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet2) + + const type = 0; + const delay = 0; + + const tx = await contract.addProxy(convertH160ToPublicKey(evmWallet3.address), type, delay) + await tx.wait() + + + const proxiesAfterAdd = await await api.query.Proxy.Proxies.getValue(convertH160ToSS58(evmWallet2.address)) + + const length = proxiesAfterAdd[0].length + assert.equal(length, proxies[0].length + 1, "proxy should be set") + const proxy = proxiesAfterAdd[0][proxiesAfterAdd[0].length - 1] + + assert.equal(proxy.delegate, convertH160ToSS58(evmWallet3.address), "proxy should be set") + + + const balance = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free + + const amount = 1000000000; + + const contract2 = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet3) + + + const callCode = await getTransferCallCode(api, receiver, amount) + const tx2 = await contract2.proxyCall(convertH160ToPublicKey(evmWallet2.address), [type], callCode) + await tx2.wait() + + const balanceAfter = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free + assert.equal(balanceAfter, balance + BigInt(amount), "balance should be increased") + }) +}); diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index f8f9a56838..de6ac5825b 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -139,8 +139,8 @@ pub mod pallet { Alpha, /// Enum for crowdloan precompile Crowdloan, - /// Pure proxy precompile - PureProxy, + /// Proxy precompile + Proxy, /// Leasing precompile Leasing, } diff --git a/pallets/proxy/src/lib.rs b/pallets/proxy/src/lib.rs index 93ac568668..70337fc7a8 100644 --- a/pallets/proxy/src/lib.rs +++ b/pallets/proxy/src/lib.rs @@ -39,6 +39,7 @@ use frame::{ prelude::*, traits::{Currency, InstanceFilter, ReservableCurrency}, }; +use frame_system::pallet_prelude::BlockNumberFor as SystemBlockNumberFor; pub use pallet::*; use subtensor_macros::freeze_struct; pub use weights::WeightInfo; @@ -752,6 +753,14 @@ pub mod pallet { InvalidDerivedAccountId, } + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_finalize(_n: SystemBlockNumberFor) { + // clear this map on end of each block + let _ = LastCallResult::::clear(u32::MAX, None); + } + } + /// The set of account proxies. Maps the account which has delegated to the accounts /// which are being delegated to, together with the amount held on deposit. #[pallet::storage] @@ -782,6 +791,11 @@ pub mod pallet { ValueQuery, >; + /// The result of the last call made by the proxy (key). + #[pallet::storage] + pub type LastCallResult = + StorageMap<_, Twox64Concat, T::AccountId, DispatchResult, OptionQuery>; + #[pallet::view_functions] impl Pallet { /// Check if a `RuntimeCall` is allowed for a given `ProxyType`. @@ -1028,7 +1042,7 @@ impl Pallet { ) { use frame::traits::{InstanceFilter as _, OriginTrait as _}; // This is a freshly authenticated new account, the origin restrictions doesn't apply. - let mut origin: T::RuntimeOrigin = frame_system::RawOrigin::Signed(real).into(); + let mut origin: T::RuntimeOrigin = frame_system::RawOrigin::Signed(real.clone()).into(); origin.add_filter(move |c: &::RuntimeCall| { let c = ::RuntimeCall::from_ref(c); // We make sure the proxy call does access this pallet to change modify proxies. @@ -1052,6 +1066,9 @@ impl Pallet { } }); let e = call.dispatch(origin); + + LastCallResult::::insert(real, e.map(|_| ()).map_err(|e| e.error)); + Self::deposit_event(Event::ProxyExecuted { result: e.map(|_| ()).map_err(|e| e.error), }); diff --git a/pallets/subtensor/Cargo.toml b/pallets/subtensor/Cargo.toml index fdd5e5f9ab..2e35a89d19 100644 --- a/pallets/subtensor/Cargo.toml +++ b/pallets/subtensor/Cargo.toml @@ -59,6 +59,8 @@ pallet-subtensor-proxy.workspace = true [dev-dependencies] pallet-balances = { workspace = true, features = ["std"] } pallet-scheduler.workspace = true +pallet-subtensor-proxy.workspace = true +subtensor-runtime-common.workspace = true pallet-subtensor-swap.workspace = true sp-version.workspace = true # Substrate diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index ddd259441e..090fcf8f75 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -8,7 +8,7 @@ use core::num::NonZeroU64; use crate::utils::rate_limiting::TransactionType; use crate::*; -use frame_support::traits::{Contains, Everything, InherentBuilder, InsideBoth}; +use frame_support::traits::{Contains, Everything, InherentBuilder, InsideBoth, InstanceFilter}; use frame_support::weights::Weight; use frame_support::weights::constants::RocksDbWeight; use frame_support::{PalletId, derive_impl}; @@ -18,6 +18,7 @@ use frame_support::{ }; use frame_system as system; use frame_system::{EnsureRoot, RawOrigin, limits, offchain::CreateTransactionBase}; +use pallet_subtensor_proxy as pallet_proxy; use pallet_subtensor_utility as pallet_utility; use sp_core::{ConstU64, Get, H256, U256, offchain::KeyTypeId}; use sp_runtime::Perbill; @@ -30,7 +31,6 @@ use sp_tracing::tracing_subscriber; use subtensor_runtime_common::{NetUid, TaoCurrency}; use subtensor_swap_interface::{Order, SwapHandler}; use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt}; - type Block = frame_system::mocking::MockBlock; // Configure a mock runtime to test the pallet. @@ -46,6 +46,7 @@ frame_support::construct_runtime!( Drand: pallet_drand::{Pallet, Call, Storage, Event} = 11, Swap: pallet_subtensor_swap::{Pallet, Call, Storage, Event} = 12, Crowdloan: pallet_crowdloan::{Pallet, Call, Storage, Event} = 13, + Proxy: pallet_subtensor_proxy = 14, } ); @@ -425,6 +426,51 @@ impl pallet_crowdloan::Config for Test { type MaxContributors = MaxContributors; } +// Proxy Pallet config +parameter_types! { + // Set as 1 for testing purposes + pub const ProxyDepositBase: Balance = 1; + // Set as 1 for testing purposes + pub const ProxyDepositFactor: Balance = 1; + // Set as 20 for testing purposes + pub const MaxProxies: u32 = 20; // max num proxies per acct + // Set as 15 for testing purposes + pub const MaxPending: u32 = 15; // max blocks pending ~15min + // Set as 1 for testing purposes + pub const AnnouncementDepositBase: Balance = 1; + // Set as 1 for testing purposes + pub const AnnouncementDepositFactor: Balance = 1; +} + +impl pallet_proxy::Config for Test { + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = subtensor_runtime_common::ProxyType; + type ProxyDepositBase = ProxyDepositBase; + type ProxyDepositFactor = ProxyDepositFactor; + type MaxProxies = MaxProxies; + type WeightInfo = pallet_proxy::weights::SubstrateWeight; + type MaxPending = MaxPending; + type CallHasher = BlakeTwo256; + type AnnouncementDepositBase = AnnouncementDepositBase; + type AnnouncementDepositFactor = AnnouncementDepositFactor; + type BlockNumberProvider = System; +} + +impl InstanceFilter for subtensor_runtime_common::ProxyType { + fn filter(&self, _c: &RuntimeCall) -> bool { + // In tests, allow all proxy types to pass through + true + } + fn is_superset(&self, o: &Self) -> bool { + match (self, o) { + (x, y) if x == y => true, + (subtensor_runtime_common::ProxyType::Any, _) => true, + _ => false, + } + } +} + mod test_crypto { use super::KEY_TYPE; use sp_core::{ @@ -570,6 +616,7 @@ pub fn test_ext_with_balances(balances: Vec<(U256, u128)>) -> sp_io::TestExterna pub(crate) fn step_block(n: u16) { for _ in 0..n { Scheduler::on_finalize(System::block_number()); + Proxy::on_finalize(System::block_number()); SubtensorModule::on_finalize(System::block_number()); System::on_finalize(System::block_number()); System::set_block_number(System::block_number() + 1); diff --git a/precompiles/src/lib.rs b/precompiles/src/lib.rs index 6b1a004ef2..8069a1eb92 100644 --- a/precompiles/src/lib.rs +++ b/precompiles/src/lib.rs @@ -34,6 +34,7 @@ use crate::extensions::*; use crate::leasing::*; use crate::metagraph::*; use crate::neuron::*; +use crate::proxy::*; use crate::sr25519::*; use crate::staking::*; use crate::storage_query::*; @@ -48,6 +49,7 @@ mod extensions; mod leasing; mod metagraph; mod neuron; +mod proxy; mod sr25519; mod staking; mod storage_query; @@ -108,7 +110,7 @@ where Self(Default::default()) } - pub fn used_addresses() -> [H160; 24] { + pub fn used_addresses() -> [H160; 25] { [ hash(1), hash(2), @@ -134,6 +136,7 @@ where hash(AlphaPrecompile::::INDEX), hash(CrowdloanPrecompile::::INDEX), hash(LeasingPrecompile::::INDEX), + hash(ProxyPrecompile::::INDEX), ] } } @@ -220,6 +223,9 @@ where a if a == hash(LeasingPrecompile::::INDEX) => { LeasingPrecompile::::try_execute::(handle, PrecompileEnum::Leasing) } + a if a == hash(ProxyPrecompile::::INDEX) => { + ProxyPrecompile::::try_execute::(handle, PrecompileEnum::Proxy) + } _ => None, } } diff --git a/precompiles/src/proxy.rs b/precompiles/src/proxy.rs new file mode 100644 index 0000000000..1399177766 --- /dev/null +++ b/precompiles/src/proxy.rs @@ -0,0 +1,242 @@ +use core::marker::PhantomData; + +use crate::{PrecompileExt, PrecompileHandleExt}; + +use alloc::format; +use fp_evm::{ExitError, PrecompileFailure}; +use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; +use frame_system::RawOrigin; +use pallet_evm::{AddressMapping, PrecompileHandle}; +use pallet_subtensor_proxy as pallet_proxy; +use precompile_utils::EvmResult; +use sp_core::H256; +use sp_runtime::{ + codec::DecodeLimit, + traits::{Dispatchable, StaticLookup}, +}; +use sp_std::boxed::Box; +use sp_std::vec::Vec; +use subtensor_runtime_common::ProxyType; +pub struct ProxyPrecompile(PhantomData); +const MAX_DECODE_DEPTH: u32 = 8; + +impl PrecompileExt for ProxyPrecompile +where + R: frame_system::Config + + pallet_evm::Config + + pallet_subtensor::Config + + pallet_proxy::Config, + R::AccountId: From<[u8; 32]> + Into<[u8; 32]>, + ::AddressMapping: AddressMapping, + ::RuntimeCall: From> + + From> + + GetDispatchInfo + + Dispatchable, + ::AddressMapping: AddressMapping, + <::Lookup as StaticLookup>::Source: From, +{ + const INDEX: u64 = 2059; +} + +#[precompile_utils::precompile] +impl ProxyPrecompile +where + R: frame_system::Config + + pallet_evm::Config + + pallet_subtensor::Config + + pallet_proxy::Config, + R::AccountId: From<[u8; 32]> + Into<[u8; 32]>, + ::AddressMapping: AddressMapping, + ::RuntimeCall: From> + + From> + + GetDispatchInfo + + Dispatchable, + <::Lookup as StaticLookup>::Source: From, +{ + #[precompile::public("createPureProxy(uint8,uint32,uint16)")] + pub fn create_pure_proxy( + handle: &mut impl PrecompileHandle, + proxy_type_: u8, + delay: u32, + index: u16, + ) -> EvmResult { + let account_id = handle.caller_account_id::(); + let proxy_type = + ProxyType::try_from(proxy_type_).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid proxy type".into()), + })?; + + let call = pallet_proxy::Call::::create_pure { + proxy_type, + delay: delay.into(), + index, + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id.clone()))?; + + // Success! + // Try to get proxy address + let proxy_address: [u8; 32] = + pallet_proxy::pallet::Pallet::::pure_account(&account_id, &proxy_type, index, None) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Proxy not found".into()), + })? + .into(); + + // Check if in the proxies map + let proxy_entry = pallet_proxy::pallet::Pallet::::proxies(proxy_address.into()); + if proxy_entry + .0 + .iter() + .any(|p| account_id == p.delegate && proxy_type == p.proxy_type) + { + return Ok(proxy_address.into()); + } + + Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Proxy not found".into()), + }) + } + + #[precompile::public("killPureProxy(bytes32,uint8,uint16,uint32,uint32)")] + pub fn kill_pure_proxy( + handle: &mut impl PrecompileHandle, + spawner: H256, + proxy_type: u8, + index: u16, + height: u32, + ext_index: u32, + ) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let proxy_type = ProxyType::try_from(proxy_type).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid proxy type".into()), + })?; + + let call = pallet_proxy::Call::::kill_pure { + spawner: <::Lookup as StaticLookup>::Source::from( + spawner.0.into(), + ), + proxy_type, + index, + height: height.into(), + ext_index: ext_index.into(), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + #[precompile::public("proxyCall(bytes32,uint8[],uint8[])")] + pub fn proxy_call( + handle: &mut impl PrecompileHandle, + real: H256, + force_proxy_type: Vec, + call: Vec, + ) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + + let call = ::RuntimeCall::decode_with_depth_limit( + MAX_DECODE_DEPTH, + &mut &call[..], + ) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("The raw call data not correctly encoded".into()), + })?; + + let mut proxy_type: Option = None; + if let Some(p) = force_proxy_type.first() { + let proxy_type_ = ProxyType::try_from(*p).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid proxy type".into()), + })?; + proxy_type = Some(proxy_type_); + }; + + let call = pallet_proxy::Call::::proxy { + real: <::Lookup as StaticLookup>::Source::from( + real.0.into(), + ), + force_proxy_type: proxy_type, + call: Box::new(call), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id))?; + + let real_account_id = R::AccountId::from(real.0.into()); + + let last_call_result = pallet_proxy::LastCallResult::::get(real_account_id); + match last_call_result { + Some(last_call_result) => match last_call_result { + Ok(()) => Ok(()), + Err(e) => Err(PrecompileFailure::Error { + exit_status: ExitError::Other(format!("{e:?}").into()), + }), + }, + None => Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Proxy execution failed".into()), + }), + } + } + + #[precompile::public("addProxy(bytes32,uint8,uint32)")] + pub fn add_proxy( + handle: &mut impl PrecompileHandle, + delegate: H256, + proxy_type: u8, + delay: u32, + ) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let proxy_type = ProxyType::try_from(proxy_type).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid proxy type".into()), + })?; + + let call = pallet_proxy::Call::::add_proxy { + delegate: <::Lookup as StaticLookup>::Source::from( + delegate.0.into(), + ), + proxy_type, + delay: delay.into(), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + #[precompile::public("removeProxy(bytes32,uint8,uint32)")] + pub fn remove_proxy( + handle: &mut impl PrecompileHandle, + delegate: H256, + proxy_type: u8, + delay: u32, + ) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let proxy_type = ProxyType::try_from(proxy_type).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid proxy type".into()), + })?; + + let call = pallet_proxy::Call::::remove_proxy { + delegate: <::Lookup as StaticLookup>::Source::from( + delegate.0.into(), + ), + proxy_type, + delay: delay.into(), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + #[precompile::public("removeProxies()")] + pub fn remove_proxies(handle: &mut impl PrecompileHandle) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + + let call = pallet_proxy::Call::::remove_proxies {}; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + #[precompile::public("pokeDeposit()")] + pub fn poke_deposit(handle: &mut impl PrecompileHandle) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + + let call = pallet_proxy::Call::::poke_deposit {}; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } +} diff --git a/precompiles/src/solidity/proxy.sol b/precompiles/src/solidity/proxy.sol new file mode 100644 index 0000000000..b0e03031bf --- /dev/null +++ b/precompiles/src/solidity/proxy.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +address constant IPROXY_ADDRESS = 0x000000000000000000000000000000000000080b; + +interface IProxy { + function createPureProxy( + uint8 proxy_type, + uint32 delay, + uint16 index + ) external; + + function proxyCall(bytes32 real, uint8[] memory force_proxy_type, bytes memory call) external; + + function killPureProxy( + bytes32 spawner, + uint8 proxy_type, + uint16 index, + uint16 height, + uint32 ext_index + ) external; + + function addProxy(bytes32 delegate, uint8 proxy_type, uint32 delay) external; + + function removeProxy(bytes32 delegate, uint8 proxy_type, uint32 delay) external; + + function removeProxies() external; + + function pokeDeposit() external; +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 5e17dc6f7d..f6843c50ee 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -220,7 +220,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 345, + spec_version: 346, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,