From 9aae4aeae4be9f8f8694a94956fd245a3c9c7c8b Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Wed, 17 Jan 2024 16:50:35 +0100 Subject: [PATCH 01/20] Added rescue lockdrop precompile & tests --- Cargo.lock | 30 +++ pallets/unified-accounts/src/lib.rs | 11 +- pallets/unified-accounts/src/tests.rs | 32 +++ precompiles/rescue-lockdrop/Cargo.toml | 62 +++++ .../rescue-lockdrop/RescueLockdrop.sol | 14 ++ precompiles/rescue-lockdrop/src/lib.rs | 133 ++++++++++ precompiles/rescue-lockdrop/src/mock.rs | 236 ++++++++++++++++++ precompiles/rescue-lockdrop/src/tests.rs | 103 ++++++++ 8 files changed, 616 insertions(+), 5 deletions(-) create mode 100644 precompiles/rescue-lockdrop/Cargo.toml create mode 100644 precompiles/rescue-lockdrop/RescueLockdrop.sol create mode 100644 precompiles/rescue-lockdrop/src/lib.rs create mode 100644 precompiles/rescue-lockdrop/src/mock.rs create mode 100644 precompiles/rescue-lockdrop/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index aed6afc6ae..8dd1c5cd4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8028,6 +8028,35 @@ dependencies = [ "num", ] +[[package]] +name = "pallet-evm-precompile-rescue-lockdrop" +version = "0.1.0" +dependencies = [ + "astar-primitives", + "derive_more", + "ethers", + "fp-evm", + "frame-support", + "frame-system", + "hex", + "hex-literal", + "libsecp256k1", + "log", + "num_enum 0.5.11", + "pallet-balances", + "pallet-evm", + "pallet-timestamp", + "pallet-unified-accounts", + "parity-scale-codec", + "precompile-utils", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-evm-precompile-sha3fips" version = "2.0.0-dev" @@ -8858,6 +8887,7 @@ dependencies = [ "parity-scale-codec", "precompile-utils", "scale-info", + "signature 1.6.4", "sp-core", "sp-io", "sp-runtime", diff --git a/pallets/unified-accounts/src/lib.rs b/pallets/unified-accounts/src/lib.rs index 358f4c12b3..a88f123cee 100644 --- a/pallets/unified-accounts/src/lib.rs +++ b/pallets/unified-accounts/src/lib.rs @@ -76,6 +76,7 @@ use frame_support::{ }, }; use frame_system::{ensure_signed, pallet_prelude::*}; +pub use pallet::*; use pallet_evm::AddressMapping; use precompile_utils::keccak256; use sp_core::{H160, H256, U256}; @@ -86,8 +87,6 @@ use sp_runtime::{ }; use sp_std::marker::PhantomData; -pub use pallet::*; - pub mod weights; pub use weights::WeightInfo; @@ -296,12 +295,14 @@ impl Pallet { keccak_256(&payload) } - pub fn verify_signature(who: &T::AccountId, sig: &EvmSignature) -> Option { + pub fn recover_pubkey(who: &T::AccountId, sig: &EvmSignature) -> Option<[u8; 64]> { let payload_hash = Self::build_signing_payload(who); + sp_io::crypto::secp256k1_ecdsa_recover(sig, &payload_hash).ok() + } - sp_io::crypto::secp256k1_ecdsa_recover(sig, &payload_hash) + pub fn verify_signature(who: &T::AccountId, sig: &EvmSignature) -> Option { + Self::recover_pubkey(who, sig) .map(|pubkey| H160::from(H256::from_slice(&keccak_256(&pubkey)))) - .ok() } fn build_domain_separator() -> [u8; 32] { diff --git a/pallets/unified-accounts/src/tests.rs b/pallets/unified-accounts/src/tests.rs index 0896ef2529..7f766a3431 100644 --- a/pallets/unified-accounts/src/tests.rs +++ b/pallets/unified-accounts/src/tests.rs @@ -397,3 +397,35 @@ fn charging_storage_fee_should_not_reap_account() { ); }); } + +#[test] +fn recover_public_key_works() { + ExtBuilder::default().build().execute_with(|| { + let claim = Claim { + substrate_address: ALICE.encode().into(), + }; + + let claim_hash = UnifiedAccounts::build_signing_payload(&ALICE); + // assert signing payload is correct + assert_eq!( + claim.encode_eip712().unwrap(), + claim_hash, + "signing payload should match" + ); + + // sign the payload + let signature = UnifiedAccounts::eth_sign_prehash(&claim_hash, &alice_secret()); + + let expected_pubkey = + &libsecp256k1::PublicKey::from_secret_key(&alice_secret()).serialize()[1..65]; + + // assert public key recovery works + assert_eq!( + expected_pubkey, + UnifiedAccounts::recover_pubkey(&ALICE, &signature) + .unwrap() + .as_ref(), + "recover public key should work" + ); + }); +} diff --git a/precompiles/rescue-lockdrop/Cargo.toml b/precompiles/rescue-lockdrop/Cargo.toml new file mode 100644 index 0000000000..115f8f129b --- /dev/null +++ b/precompiles/rescue-lockdrop/Cargo.toml @@ -0,0 +1,62 @@ +[package] +name = "pallet-evm-precompile-rescue-lockdrop" +description = "Evm Precompile to rescue lockdrop accounts" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +hex = { workspace = true } +log = { workspace = true } +num_enum = { workspace = true } +precompile-utils = { workspace = true } + +# Substrate +frame-support = { workspace = true } +frame-system = { workspace = true } +parity-scale-codec = { workspace = true, features = ["max-encoded-len"] } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +# Frontier +fp-evm = { workspace = true } +pallet-evm = { workspace = true } +pallet-unified-accounts = { workspace = true } + +# Astar +astar-primitives = { workspace = true } + +[dev-dependencies] +derive_more = { workspace = true } +hex-literal = { workspace = true } +scale-info = { workspace = true } +serde = { workspace = true } + +precompile-utils = { workspace = true, features = ["testing"] } + +ethers = { workspace = true } +libsecp256k1 = { workspace = true, features = ["hmac", "static-context"] } +pallet-balances = { workspace = true, features = ["std"] } +pallet-timestamp = { workspace = true } +sp-runtime = { workspace = true } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "pallet-unified-accounts/std", + "fp-evm/std", + "frame-support/std", + "frame-system/std", + "pallet-evm/std", + "precompile-utils/std", + "sp-core/std", + "sp-std/std", + "sp-io/std", + "sp-runtime/std", + "astar-primitives/std", +] diff --git a/precompiles/rescue-lockdrop/RescueLockdrop.sol b/precompiles/rescue-lockdrop/RescueLockdrop.sol new file mode 100644 index 0000000000..363648d3f4 --- /dev/null +++ b/precompiles/rescue-lockdrop/RescueLockdrop.sol @@ -0,0 +1,14 @@ +pragma solidity ^0.8.0; + +/** + * @title Rescue Lockdrop interface. + */ + +/// Interface to the rescue lockdrop precompiled contract +/// Predeployed at the address 0x0000000000000000000000000000000000005007 +interface RescueLockdrop { + function claim_lock_drop_account( + bytes32 accountId, + bytes signature + ) external returns (bool); +} diff --git a/precompiles/rescue-lockdrop/src/lib.rs b/precompiles/rescue-lockdrop/src/lib.rs new file mode 100644 index 0000000000..77bcacd4cf --- /dev/null +++ b/precompiles/rescue-lockdrop/src/lib.rs @@ -0,0 +1,133 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +use core::marker::PhantomData; +use fp_evm::PrecompileHandle; +use frame_support::pallet_prelude::IsType; +use frame_support::{ + dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, + traits::ConstU32, +}; +use frame_system::Config; +use precompile_utils::prelude::BoundedBytes; +use precompile_utils::prelude::RuntimeHelper; +use precompile_utils::EvmResult; +use sp_core::ecdsa; +use sp_core::ecdsa::Signature; +use sp_core::{crypto::AccountId32, H160, H256}; +use sp_io::hashing::keccak_256; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +// ECDSA signature bytes +type ECDSASignatureBytes = ConstU32<65>; + +/// A precompile to unify lock drop account. +pub struct UnifyLockdropPrecompile(PhantomData); + +#[precompile_utils::precompile] +impl UnifyLockdropPrecompile +where + R: pallet_evm::Config + pallet_unified_accounts::Config, + ::RuntimeOrigin: From>, + R::RuntimeCall: Dispatchable + GetDispatchInfo, + R::RuntimeCall: From>, + ::AccountId: IsType, + ::AccountId: From<[u8; 32]>, +{ + #[precompile::public("claim_lock_drop_account(bytes32,bytes)")] + fn claim_lock_drop_account( + handle: &mut impl PrecompileHandle, + account_id: H256, + signature: BoundedBytes, + ) -> EvmResult { + log::trace!( + target: "rescue-lockdrop-precompile:claim_lock_drop_account", + "raw arguments: account_id: {:?}, signature: {:?}", + account_id, + signature + ); + + let caller = handle.context().caller.into(); + let signature_bytes: Vec = signature.into(); + let account_id = AccountId32::new(account_id.into()).into(); + + let signature_opt = Self::parse_signature(&signature_bytes); + + let pubkey = match >::recover_pubkey( + &account_id, + signature_opt.as_ref(), + ) { + Some(k) => k, + None => { + log::trace!( + target: "rescue-lockdrop-precompile:claim_lock_drop_account", + "Error: could not recover pubkey from signature" + ); + return Ok(false); + } + }; + + if caller != Self::get_evm_address_from_pubkey(&pubkey) { + log::trace!( + target: "rescue-lockdrop-precompile:claim_lock_drop_account", + "Error: caller does not match calculated EVM address" + ); + return Ok(false); + } + + let origin = Self::get_account_id_from_pubkey(pubkey); + + let call = pallet_unified_accounts::Call::::claim_evm_address { + evm_address: caller, + signature: signature_opt.into(), + }; + + match RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call) { + Ok(_) => Ok(true), + Err(e) => { + log::trace!( + target: "rescue-lockdrop-precompile:claim_lock_drop_account", + "Error: {:?}", + e + ); + Ok(false) + } + } + } + + fn get_account_id_from_pubkey(pubkey: [u8; 64]) -> ::AccountId { + let origin = + sp_io::hashing::blake2_256(ecdsa::Public::from_full(pubkey.as_ref()).unwrap().as_ref()) + .into(); + origin + } + + fn parse_signature(signature_bytes: &Vec) -> Signature { + ecdsa::Signature::from_slice(&signature_bytes[..]).unwrap() + } + + fn get_evm_address_from_pubkey(pubkey: &[u8]) -> H160 { + H160::from(H256::from_slice(&keccak_256(pubkey))) + } +} diff --git a/precompiles/rescue-lockdrop/src/mock.rs b/precompiles/rescue-lockdrop/src/mock.rs new file mode 100644 index 0000000000..37cd27cbd8 --- /dev/null +++ b/precompiles/rescue-lockdrop/src/mock.rs @@ -0,0 +1,236 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +use super::*; + +use fp_evm::{IsPrecompileResult, Precompile}; +use frame_support::{construct_runtime, parameter_types, traits::ConstU64, weights::Weight}; +pub use pallet_evm::{ + AddressMapping, EnsureAddressNever, EnsureAddressRoot, PrecompileResult, PrecompileSet, +}; +use parity_scale_codec::Encode; +use sp_core::{keccak_256, H160, H256}; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, ConstU32, IdentityLookup}, + AccountId32, +}; + +use ethers::contract::{Eip712, EthAbiType}; +use ethers::prelude::transaction::eip712::Eip712; + +use astar_primitives::evm::HashedDefaultMappings; +pub type AccountId = AccountId32; +pub type Balance = u128; +pub type BlockNumber = u64; +pub type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +pub type Block = frame_system::mocking::MockBlock; + +pub const PRECOMPILE_ADDRESS: H160 = H160::repeat_byte(0x7B); + +pub fn alice_secret() -> libsecp256k1::SecretKey { + libsecp256k1::SecretKey::parse(&keccak_256(b"Alice")).unwrap() +} +/// EIP712 Payload struct +#[derive(Eip712, EthAbiType, Clone)] +#[eip712( +name = "Astar EVM Claim", +version = "1", +chain_id = 1024, +// mock genisis hash +raw_salt = "0x4545454545454545454545454545454545454545454545454545454545454545" +)] +struct Claim { + substrate_address: ethers::core::types::Bytes, +} + +/// Build the signature payload for given native account and eth private key +pub fn get_evm_signature(who: &AccountId32, secret: &libsecp256k1::SecretKey) -> [u8; 65] { + // sign the payload + UnifiedAccounts::eth_sign_prehash( + &Claim { + substrate_address: who.encode().into(), + } + .encode_eip712() + .unwrap(), + secret, + ) +} + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, 0)); +} + +impl frame_system::Config for TestRuntime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type RuntimeCall = RuntimeCall; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId32; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +#[derive(Debug, Clone, Copy)] +pub struct TestPrecompileSet(PhantomData); + +impl PrecompileSet for TestPrecompileSet +where + R: pallet_evm::Config, + UnifyLockdropPrecompile: Precompile, +{ + fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { + match handle.code_address() { + a if a == PRECOMPILE_ADDRESS => Some(UnifyLockdropPrecompile::::execute(handle)), + _ => None, + } + } + + fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: address == PRECOMPILE_ADDRESS, + extra_cost: 0, + } + } +} + +parameter_types! { + pub const ExistentialDeposit: u128 = 1; +} + +impl pallet_balances::Config for TestRuntime { + type MaxReserves = (); + type ReserveIdentifier = (); + type MaxLocks = (); + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type HoldIdentifier = (); + type FreezeIdentifier = (); + type MaxHolds = ConstU32<0>; + type MaxFreezes = ConstU32<0>; +} + +parameter_types! { + pub const MinimumPeriod: u64 = 5; +} + +impl pallet_timestamp::Config for TestRuntime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +parameter_types! { + pub const PrecompilesValue: TestPrecompileSet = + TestPrecompileSet(PhantomData); + pub WeightPerGas: Weight = Weight::from_parts(1, 0); +} + +pub type PrecompileCall = UnifyLockdropPrecompileCall; + +impl pallet_evm::Config for TestRuntime { + type FeeCalculator = (); + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; + type WeightPerGas = WeightPerGas; + type CallOrigin = EnsureAddressRoot; + type WithdrawOrigin = EnsureAddressNever; + type AddressMapping = UnifiedAccounts; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type Runner = pallet_evm::runner::stack::Runner; + type PrecompilesType = TestPrecompileSet; + type PrecompilesValue = PrecompilesValue; + type Timestamp = Timestamp; + type ChainId = ChainId; + type OnChargeTransaction = (); + type BlockGasLimit = (); + type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; + type FindAuthor = (); + type OnCreate = (); + type WeightInfo = (); + type GasLimitPovSizeRatio = ConstU64<4>; +} + +parameter_types! { + // 2 storage items with value size 20 and 32 + pub const AccountMappingStorageFee: u128 = 0; + pub ChainId: u64 = 1024; +} + +impl pallet_unified_accounts::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type DefaultMappings = HashedDefaultMappings; + type ChainId = ChainId; + type AccountMappingStorageFee = AccountMappingStorageFee; + type WeightInfo = pallet_unified_accounts::weights::SubstrateWeight; +} + +// Configure a mock runtime to test the pallet. +construct_runtime!( + pub enum TestRuntime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Evm: pallet_evm, + UnifiedAccounts: pallet_unified_accounts, + Balances : pallet_balances, + Timestamp: pallet_timestamp, + } +); + +#[derive(Default)] +pub(crate) struct ExtBuilder; + +impl ExtBuilder { + pub(crate) fn build(self) -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::default() + .build_storage::() + .expect("Frame system builds valid default genesis config"); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} diff --git a/precompiles/rescue-lockdrop/src/tests.rs b/precompiles/rescue-lockdrop/src/tests.rs new file mode 100644 index 0000000000..612024658f --- /dev/null +++ b/precompiles/rescue-lockdrop/src/tests.rs @@ -0,0 +1,103 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +use ethers::prelude::H256; +use sp_core::crypto::AccountId32; + +use crate::mock::*; + +use astar_primitives::evm::UnifiedAddressMapper; +use precompile_utils::testing::*; +use sp_core::ecdsa; + +fn precompiles() -> TestPrecompileSet { + PrecompilesValue::get() +} + +#[test] +fn unify_lockdrop_account_works() { + ExtBuilder::default().build().execute_with(|| { + // Get Alice EVM address based on the Public Key + let alice_eth = UnifiedAccounts::eth_address(&alice_secret()); + // Get derived AccountId from the Blake2b hash of the compressed ECDSA Public key + let account_id = account_id(&alice_secret()); + // Sign the EIP712 payload to Claim EVM address + let sig = get_evm_signature(&account_id, &alice_secret()); + + precompiles() + .prepare_test( + alice_eth, + PRECOMPILE_ADDRESS, + PrecompileCall::claim_lock_drop_account { + account_id: H256::from_slice(account_id.as_ref()), + signature: sig.into(), + }, + ) + .expect_no_logs() + .execute_returns(true); + + // Ensure that the AccountId mapped with Alice EVM account is the AccountId from the Blake2b hash of the compressed ECDSA Public key + let alice_account_id = UnifiedAccounts::into_account_id(alice_eth); + assert_eq!(alice_account_id, account_id); + }); +} + +#[test] +fn wrong_caller_cannot_unify_derived_address() { + ExtBuilder::default().build().execute_with(|| { + // Get Alice EVM address based on the Public Key + let alice_eth = UnifiedAccounts::eth_address(&alice_secret()); + // Get derived AccountId from the Blake2b hash of the compressed ECDSA Public key + let account_id = account_id(&alice_secret()); + // Sign the EIP712 payload to Claim EVM address + let sig = get_evm_signature(&account_id, &alice_secret()); + + precompiles() + .prepare_test( + Bob, + PRECOMPILE_ADDRESS, + PrecompileCall::claim_lock_drop_account { + account_id: H256::from_slice(account_id.as_ref()), + signature: sig.into(), + }, + ) + .expect_no_logs() + .execute_returns(false); + + // Ensure that the AccountId mapped with Alice EVM account is the default AccountId (not the AccountId from the Blake2b hash of the compressed ECDSA Public key) + // This means account has not been mapped + let alice_account_id = UnifiedAccounts::into_account_id(alice_eth); + let alice_default_account_id = + ::DefaultMappings::to_default_account_id( + &alice_eth, + ); + + assert_eq!(alice_account_id, alice_default_account_id); + }); +} + +fn account_id(secret: &libsecp256k1::SecretKey) -> AccountId32 { + sp_io::hashing::blake2_256( + ecdsa::Public::from_full( + &libsecp256k1::PublicKey::from_secret_key(secret).serialize()[1..65], + ) + .unwrap() + .as_ref(), + ) + .into() +} From e9c0c8dc866e1a939ba9558d52454438973176a0 Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Thu, 18 Jan 2024 11:36:10 +0100 Subject: [PATCH 02/20] Added to local & Shibuya --- Cargo.lock | 8 ++---- Cargo.toml | 1 + precompiles/rescue-lockdrop/Cargo.toml | 39 +++++++++++--------------- precompiles/rescue-lockdrop/src/lib.rs | 35 +++++++++++++++++------ runtime/local/Cargo.toml | 2 ++ runtime/local/src/precompiles.rs | 2 ++ runtime/shibuya/Cargo.toml | 2 ++ runtime/shibuya/src/precompiles.rs | 2 ++ 8 files changed, 54 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8dd1c5cd4e..0076b4357b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6051,6 +6051,7 @@ dependencies = [ "pallet-evm-precompile-dispatch", "pallet-evm-precompile-ed25519", "pallet-evm-precompile-modexp", + "pallet-evm-precompile-rescue-lockdrop", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", "pallet-evm-precompile-sr25519", @@ -8033,16 +8034,12 @@ name = "pallet-evm-precompile-rescue-lockdrop" version = "0.1.0" dependencies = [ "astar-primitives", - "derive_more", "ethers", "fp-evm", "frame-support", "frame-system", - "hex", - "hex-literal", "libsecp256k1", "log", - "num_enum 0.5.11", "pallet-balances", "pallet-evm", "pallet-timestamp", @@ -8050,7 +8047,6 @@ dependencies = [ "parity-scale-codec", "precompile-utils", "scale-info", - "serde", "sp-core", "sp-io", "sp-runtime", @@ -8887,7 +8883,6 @@ dependencies = [ "parity-scale-codec", "precompile-utils", "scale-info", - "signature 1.6.4", "sp-core", "sp-io", "sp-runtime", @@ -13250,6 +13245,7 @@ dependencies = [ "pallet-evm-precompile-dispatch", "pallet-evm-precompile-ed25519", "pallet-evm-precompile-modexp", + "pallet-evm-precompile-rescue-lockdrop", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", "pallet-evm-precompile-sr25519", diff --git a/Cargo.toml b/Cargo.toml index b55af3035a..d92d9c92f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -297,6 +297,7 @@ pallet-evm-precompile-xvm = { path = "./precompiles/xvm", default-features = fal pallet-evm-precompile-dapps-staking = { path = "./precompiles/dapps-staking", default-features = false } pallet-evm-precompile-dapp-staking-v3 = { path = "./precompiles/dapp-staking-v3", default-features = false } pallet-evm-precompile-unified-accounts = { path = "./precompiles/unified-accounts", default-features = false } +pallet-evm-precompile-rescue-lockdrop = { path = "./precompiles/rescue-lockdrop", default-features = false } pallet-chain-extension-xvm = { path = "./chain-extensions/xvm", default-features = false } pallet-chain-extension-assets = { path = "./chain-extensions/pallet-assets", default-features = false } diff --git a/precompiles/rescue-lockdrop/Cargo.toml b/precompiles/rescue-lockdrop/Cargo.toml index 115f8f129b..3b9b6a7f20 100644 --- a/precompiles/rescue-lockdrop/Cargo.toml +++ b/precompiles/rescue-lockdrop/Cargo.toml @@ -8,9 +8,8 @@ homepage.workspace = true repository.workspace = true [dependencies] -hex = { workspace = true } +libsecp256k1 = { workspace = true, features = ["hmac", "static-context"] } log = { workspace = true } -num_enum = { workspace = true } precompile-utils = { workspace = true } # Substrate @@ -31,32 +30,26 @@ pallet-unified-accounts = { workspace = true } astar-primitives = { workspace = true } [dev-dependencies] -derive_more = { workspace = true } -hex-literal = { workspace = true } -scale-info = { workspace = true } -serde = { workspace = true } - -precompile-utils = { workspace = true, features = ["testing"] } - ethers = { workspace = true } -libsecp256k1 = { workspace = true, features = ["hmac", "static-context"] } pallet-balances = { workspace = true, features = ["std"] } pallet-timestamp = { workspace = true } -sp-runtime = { workspace = true } +precompile-utils = { workspace = true, features = ["testing"] } +scale-info = { workspace = true } [features] default = ["std"] std = [ - "parity-scale-codec/std", - "pallet-unified-accounts/std", - "fp-evm/std", - "frame-support/std", - "frame-system/std", - "pallet-evm/std", - "precompile-utils/std", - "sp-core/std", - "sp-std/std", - "sp-io/std", - "sp-runtime/std", - "astar-primitives/std", + "parity-scale-codec/std", + "pallet-unified-accounts/std", + "fp-evm/std", + "frame-support/std", + "frame-system/std", + "pallet-evm/std", + "precompile-utils/std", + "sp-core/std", + "sp-std/std", + "sp-io/std", + "sp-runtime/std", + "astar-primitives/std", + "libsecp256k1/std", ] diff --git a/precompiles/rescue-lockdrop/src/lib.rs b/precompiles/rescue-lockdrop/src/lib.rs index 77bcacd4cf..4a0e8b48e1 100644 --- a/precompiles/rescue-lockdrop/src/lib.rs +++ b/precompiles/rescue-lockdrop/src/lib.rs @@ -33,6 +33,7 @@ use sp_core::ecdsa; use sp_core::ecdsa::Signature; use sp_core::{crypto::AccountId32, H160, H256}; use sp_io::hashing::keccak_256; +use sp_std::vec::Vec; #[cfg(test)] mod mock; @@ -72,8 +73,17 @@ where let signature_bytes: Vec = signature.into(); let account_id = AccountId32::new(account_id.into()).into(); - let signature_opt = Self::parse_signature(&signature_bytes); - + // 1. Recover the ECDSA Public key from the signature + let signature_opt = match Self::parse_signature(&signature_bytes) { + Some(s) => s, + None => { + log::trace!( + target: "rescue-lockdrop-precompile:claim_lock_drop_account", + "Error: could not parse signature" + ); + return Ok(false); + } + }; let pubkey = match >::recover_pubkey( &account_id, signature_opt.as_ref(), @@ -88,6 +98,7 @@ where } }; + // 2. Ensure that the caller matches the recovered EVM address from the signature if caller != Self::get_evm_address_from_pubkey(&pubkey) { log::trace!( target: "rescue-lockdrop-precompile:claim_lock_drop_account", @@ -96,8 +107,13 @@ where return Ok(false); } + // 3. Derive the AccountId from the ECDSA compressed Public key let origin = Self::get_account_id_from_pubkey(pubkey); + // 4. Call to Claim EVM address - it will populate the mapping between the EVM address and the derived AccountId + // As sufficient checks has been done: + // - the caller matches the recovered EVM address from the signature + // - the AccountId is derived from the recovered ECDSA Public key let call = pallet_unified_accounts::Call::::claim_evm_address { evm_address: caller, signature: signature_opt.into(), @@ -117,14 +133,17 @@ where } fn get_account_id_from_pubkey(pubkey: [u8; 64]) -> ::AccountId { - let origin = - sp_io::hashing::blake2_256(ecdsa::Public::from_full(pubkey.as_ref()).unwrap().as_ref()) - .into(); - origin + sp_io::hashing::blake2_256( + libsecp256k1::PublicKey::parse_slice(&pubkey, None) + .unwrap() + .serialize_compressed() + .as_ref(), + ) + .into() } - fn parse_signature(signature_bytes: &Vec) -> Signature { - ecdsa::Signature::from_slice(&signature_bytes[..]).unwrap() + fn parse_signature(signature_bytes: &Vec) -> Option { + ecdsa::Signature::from_slice(&signature_bytes[..]) } fn get_evm_address_from_pubkey(pubkey: &[u8]) -> H160 { diff --git a/runtime/local/Cargo.toml b/runtime/local/Cargo.toml index 14967b8a6c..1845a4e15d 100644 --- a/runtime/local/Cargo.toml +++ b/runtime/local/Cargo.toml @@ -76,6 +76,7 @@ pallet-dapps-staking = { workspace = true } pallet-dynamic-evm-base-fee = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } pallet-evm-precompile-dapp-staking-v3 = { workspace = true } +pallet-evm-precompile-rescue-lockdrop = { workspace = true } pallet-evm-precompile-sr25519 = { workspace = true } pallet-evm-precompile-substrate-ecdsa = { workspace = true } pallet-evm-precompile-unified-accounts = { workspace = true } @@ -147,6 +148,7 @@ std = [ "pallet-evm-precompile-substrate-ecdsa/std", "pallet-evm-precompile-unified-accounts/std", "pallet-evm-precompile-xvm/std", + "pallet-evm-precompile-rescue-lockdrop/std", "pallet-grandpa/std", "pallet-insecure-randomness-collective-flip/std", "pallet-preimage/std", diff --git a/runtime/local/src/precompiles.rs b/runtime/local/src/precompiles.rs index d207068f4b..5e73e99277 100644 --- a/runtime/local/src/precompiles.rs +++ b/runtime/local/src/precompiles.rs @@ -28,6 +28,7 @@ use pallet_evm_precompile_dapp_staking_v3::DappStakingV3Precompile; use pallet_evm_precompile_dispatch::Dispatch; use pallet_evm_precompile_ed25519::Ed25519Verify; use pallet_evm_precompile_modexp::Modexp; +use pallet_evm_precompile_rescue_lockdrop::UnifyLockdropPrecompile; use pallet_evm_precompile_sha3fips::Sha3FIPS256; use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; use pallet_evm_precompile_sr25519::Sr25519Precompile; @@ -116,6 +117,7 @@ pub type LocalPrecompilesSetAt = ( UnifiedAccountsPrecompile, (CallableByContract, CallableByPrecompile), >, + PrecompileAt, UnifyLockdropPrecompile, ()>, ); pub type LocalPrecompiles = PrecompileSetBuilder< diff --git a/runtime/shibuya/Cargo.toml b/runtime/shibuya/Cargo.toml index dd7e2770ce..f3816014ca 100644 --- a/runtime/shibuya/Cargo.toml +++ b/runtime/shibuya/Cargo.toml @@ -107,6 +107,7 @@ pallet-dynamic-evm-base-fee = { workspace = true } pallet-ethereum-checked = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } pallet-evm-precompile-dapp-staking-v3 = { workspace = true } +pallet-evm-precompile-rescue-lockdrop = { workspace = true } pallet-evm-precompile-sr25519 = { workspace = true } pallet-evm-precompile-substrate-ecdsa = { workspace = true } pallet-evm-precompile-unified-accounts = { workspace = true } @@ -231,6 +232,7 @@ std = [ "cumulus-pallet-dmp-queue/std", "cumulus-pallet-xcmp-queue/std", "cumulus-pallet-xcm/std", + "pallet-evm-precompile-rescue-lockdrop/std", "pallet-collator-selection/std", "frame-benchmarking/std", "frame-try-runtime/std", diff --git a/runtime/shibuya/src/precompiles.rs b/runtime/shibuya/src/precompiles.rs index c9f34a9a9d..d910e7059b 100644 --- a/runtime/shibuya/src/precompiles.rs +++ b/runtime/shibuya/src/precompiles.rs @@ -28,6 +28,7 @@ use pallet_evm_precompile_dapp_staking_v3::DappStakingV3Precompile; use pallet_evm_precompile_dispatch::Dispatch; use pallet_evm_precompile_ed25519::Ed25519Verify; use pallet_evm_precompile_modexp::Modexp; +use pallet_evm_precompile_rescue_lockdrop::UnifyLockdropPrecompile; use pallet_evm_precompile_sha3fips::Sha3FIPS256; use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; use pallet_evm_precompile_sr25519::Sr25519Precompile; @@ -125,6 +126,7 @@ pub type ShibuyaPrecompilesSetAt = ( UnifiedAccountsPrecompile, (CallableByContract, CallableByPrecompile), >, + PrecompileAt, UnifyLockdropPrecompile, ()>, ); pub type ShibuyaPrecompiles = PrecompileSetBuilder< From 0b20d111cc2732117d321b94d4131a110aded303 Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Thu, 18 Jan 2024 11:55:46 +0100 Subject: [PATCH 03/20] refactor unsafe unwrap --- precompiles/rescue-lockdrop/src/lib.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/precompiles/rescue-lockdrop/src/lib.rs b/precompiles/rescue-lockdrop/src/lib.rs index 4a0e8b48e1..1c50018991 100644 --- a/precompiles/rescue-lockdrop/src/lib.rs +++ b/precompiles/rescue-lockdrop/src/lib.rs @@ -108,7 +108,16 @@ where } // 3. Derive the AccountId from the ECDSA compressed Public key - let origin = Self::get_account_id_from_pubkey(pubkey); + let origin = match Self::get_account_id_from_pubkey(pubkey) { + Some(a) => a, + None => { + log::trace!( + target: "rescue-lockdrop-precompile:claim_lock_drop_account", + "Error: could not derive AccountId from pubkey" + ); + return Ok(false); + } + }; // 4. Call to Claim EVM address - it will populate the mapping between the EVM address and the derived AccountId // As sufficient checks has been done: @@ -132,14 +141,10 @@ where } } - fn get_account_id_from_pubkey(pubkey: [u8; 64]) -> ::AccountId { - sp_io::hashing::blake2_256( - libsecp256k1::PublicKey::parse_slice(&pubkey, None) - .unwrap() - .serialize_compressed() - .as_ref(), - ) - .into() + fn get_account_id_from_pubkey(pubkey: [u8; 64]) -> Option<::AccountId> { + libsecp256k1::PublicKey::parse_slice(&pubkey, None) + .map(|k| sp_io::hashing::blake2_256(k.serialize_compressed().as_ref()).into()) + .ok() } fn parse_signature(signature_bytes: &Vec) -> Option { From 123c2b0527edb08e40db0cb9bedbf8bff949cade Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Tue, 23 Jan 2024 16:57:55 +0100 Subject: [PATCH 04/20] Added dispatch lockdrop precompile --- Cargo.lock | 26 ++ Cargo.toml | 1 + precompiles/dispatch-lockdrop/Cargo.toml | 53 +++++ precompiles/dispatch-lockdrop/src/lib.rs | 219 +++++++++++++++++ precompiles/dispatch-lockdrop/src/mock.rs | 261 +++++++++++++++++++++ precompiles/dispatch-lockdrop/src/tests.rs | 80 +++++++ runtime/local/Cargo.toml | 2 + runtime/local/src/precompiles.rs | 12 +- 8 files changed, 652 insertions(+), 2 deletions(-) create mode 100644 precompiles/dispatch-lockdrop/Cargo.toml create mode 100644 precompiles/dispatch-lockdrop/src/lib.rs create mode 100644 precompiles/dispatch-lockdrop/src/mock.rs create mode 100644 precompiles/dispatch-lockdrop/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 6678b9d47e..a1095a2c5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6074,6 +6074,7 @@ dependencies = [ "pallet-evm-precompile-bn128", "pallet-evm-precompile-dapp-staking-v3", "pallet-evm-precompile-dispatch", + "pallet-evm-precompile-dispatch-lockdrop", "pallet-evm-precompile-ed25519", "pallet-evm-precompile-modexp", "pallet-evm-precompile-rescue-lockdrop", @@ -8036,6 +8037,31 @@ dependencies = [ "pallet-evm", ] +[[package]] +name = "pallet-evm-precompile-dispatch-lockdrop" +version = "0.1.0" +dependencies = [ + "astar-primitives", + "ethers", + "fp-evm", + "frame-support", + "frame-system", + "libsecp256k1", + "log", + "pallet-balances", + "pallet-evm", + "pallet-evm-precompile-dispatch", + "pallet-timestamp", + "pallet-unified-accounts", + "parity-scale-codec", + "precompile-utils", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-evm-precompile-ed25519" version = "2.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 032aef60a7..e6d32066b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -300,6 +300,7 @@ pallet-evm-precompile-dapps-staking = { path = "./precompiles/dapps-staking", de pallet-evm-precompile-dapp-staking-v3 = { path = "./precompiles/dapp-staking-v3", default-features = false } pallet-evm-precompile-unified-accounts = { path = "./precompiles/unified-accounts", default-features = false } pallet-evm-precompile-rescue-lockdrop = { path = "./precompiles/rescue-lockdrop", default-features = false } +pallet-evm-precompile-dispatch-lockdrop = { path = "./precompiles/dispatch-lockdrop", default-features = false } pallet-chain-extension-xvm = { path = "./chain-extensions/xvm", default-features = false } pallet-chain-extension-assets = { path = "./chain-extensions/pallet-assets", default-features = false } diff --git a/precompiles/dispatch-lockdrop/Cargo.toml b/precompiles/dispatch-lockdrop/Cargo.toml new file mode 100644 index 0000000000..acb30deebc --- /dev/null +++ b/precompiles/dispatch-lockdrop/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "pallet-evm-precompile-dispatch-lockdrop" +description = "Evm Precompile to dispatch calls for lockdrop accounts" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +frame-support = { workspace = true } +fp-evm = { workspace = true } +pallet-evm = { workspace = true } +libsecp256k1 = { workspace = true, features = ["hmac", "static-context"] } +log = { workspace = true } +precompile-utils = { workspace = true } +frame-system = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } +pallet-unified-accounts = { workspace = true } +pallet-evm-precompile-dispatch = { workspace = true } + +[dev-dependencies] +ethers = { workspace = true } +parity-scale-codec = { package = "parity-scale-codec", workspace = true } +scale-info = { workspace = true } +frame-system = { workspace = true } +pallet-balances = { workspace = true } +pallet-timestamp = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } +sp-runtime = { workspace = true } +precompile-utils = { workspace = true, features = ["testing"] } +astar-primitives = { workspace = true } + +[features] +default = ["std"] +std = [ + "pallet-unified-accounts/std", + "fp-evm/std", + "frame-support/std", + "frame-system/std", + "pallet-evm/std", + "precompile-utils/std", + "sp-core/std", + "sp-std/std", + "sp-io/std", + "libsecp256k1/std", + "pallet-evm/std", + "pallet-evm-precompile-dispatch/std", +] \ No newline at end of file diff --git a/precompiles/dispatch-lockdrop/src/lib.rs b/precompiles/dispatch-lockdrop/src/lib.rs new file mode 100644 index 0000000000..4956c15357 --- /dev/null +++ b/precompiles/dispatch-lockdrop/src/lib.rs @@ -0,0 +1,219 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use core::marker::PhantomData; +use fp_evm::PrecompileHandle; +use frame_support::pallet_prelude::IsType; +use frame_support::{codec::DecodeLimit as _, dispatch::Pays, traits::Get}; +use frame_support::{ + dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, + traits::ConstU32, +}; +use frame_system::Config; +use pallet_evm::GasWeightMapping; +use precompile_utils::prelude::{BoundedBytes, UnboundedBytes}; +use precompile_utils::EvmResult; +use sp_core::ecdsa; +use sp_core::ecdsa::Signature; +use sp_core::{crypto::AccountId32, H160, H256}; +use sp_io::hashing::keccak_256; +use sp_std::vec::Vec; +use pallet_evm_precompile_dispatch::DispatchValidateT; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +// ECDSA signature bytes +type ECDSASignatureBytes = ConstU32<65>; + +// `DecodeLimit` specifies the max depth a call can use when decoding, as unbounded depth +// can be used to overflow the stack. +// Default value is 8, which is the same as in XCM call decoding. +pub struct DispatchLockdrop>( + PhantomData<(Runtime, DispatchValidator, DecodeLimit)>, +); + +#[precompile_utils::precompile] +impl + DispatchLockdrop +where + Runtime: pallet_evm::Config + pallet_unified_accounts::Config, + ::RuntimeOrigin: From>, + Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, + ::AccountId: IsType, + ::AccountId: From<[u8; 32]>, + DispatchValidator: DispatchValidateT<::AccountId, ::RuntimeCall>, + DecodeLimit: Get, +{ + #[precompile::public("dispatch_lockdrop_call(bytes,bytes32,bytes)")] + fn dispatch_lockdrop_call( + handle: &mut impl PrecompileHandle, + call: UnboundedBytes, + account_id: H256, + signature: BoundedBytes, + ) -> EvmResult { + log::trace!( + target: "dispatch-lockdrop", + "raw arguments: call: {:?}, account_id: {:?}, signature: {:?}", + call, + account_id, + signature + ); + + let target_gas = handle.gas_limit(); + + let caller: H160 = handle.context().caller.into(); + let input: Vec = call.into(); + let signature_bytes: Vec = signature.into(); + let account_id = AccountId32::new(account_id.into()).into(); + + // 1. Decode the call + let call = + match Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*input) { + Ok(c) => c, + Err(_) => { + log::trace!( + target: "dispatch-lockdrop", + "Error: could not decode call" + ); + return Ok(false); + } + }; + + // 2. Check that gas limit is not exceeded + let info = call.get_dispatch_info(); + if let Some(gas) = target_gas { + let valid_weight = info.weight.ref_time() + <= Runtime::GasWeightMapping::gas_to_weight(gas, false).ref_time(); + if !valid_weight { + log::trace!( + target: "dispatch-lockdrop", + "Error: gas limit exceeded" + ); + return Ok(false); + } + } + + // 3. Recover the ECDSA Public key from the signature + let signature_opt = match Self::parse_signature(&signature_bytes) { + Some(s) => s, + None => { + log::trace!( + target: "dispatch-lockdrop", + "Error: could not parse signature" + ); + return Ok(false); + } + }; + let pubkey = match >::recover_pubkey( + &account_id, + signature_opt.as_ref(), + ) { + Some(k) => k, + None => { + log::trace!( + target: "dispatch-lockdrop", + "Error: could not recover pubkey from signature" + ); + return Ok(false); + } + }; + + // 4. Ensure that the caller matches the recovered EVM address from the signature + if caller != Self::get_evm_address_from_pubkey(&pubkey) { + log::trace!( + target: "dispatch-lockdrop", + "Error: caller does not match calculated EVM address" + ); + return Ok(false); + } + + // 5. Derive the AccountId from the ECDSA compressed Public key + let origin = match Self::get_account_id_from_pubkey(pubkey) { + Some(a) => a, + None => { + log::trace!( + target: "dispatch-lockdrop", + "Error: could not derive AccountId from pubkey" + ); + return Ok(false); + } + }; + + // 6. validate the call + if let Some(_) = DispatchValidator::validate_before_dispatch(&origin, &call) { + return Ok(false); + } + + handle + .record_external_cost(Some(info.weight.ref_time()), Some(info.weight.proof_size()))?; + + match call.dispatch(Some(origin).into()) { + Ok(post_info) => { + if post_info.pays_fee(&info) == Pays::Yes { + let actual_weight = post_info.actual_weight.unwrap_or(info.weight); + let cost = Runtime::GasWeightMapping::weight_to_gas(actual_weight); + handle.record_cost(cost)?; + + handle.refund_external_cost( + Some( + info.weight + .ref_time() + .saturating_sub(actual_weight.ref_time()), + ), + Some( + info.weight + .proof_size() + .saturating_sub(actual_weight.proof_size()), + ), + ); + } + + Ok(true) + } + Err(e) => { + log::trace!( + target: "rescue-lockdrop-precompile:claim_lock_drop_account", + "Error: {:?}", + e + ); + Ok(false) + } + } + } + + fn get_account_id_from_pubkey(pubkey: [u8; 64]) -> Option<::AccountId> { + libsecp256k1::PublicKey::parse_slice(&pubkey, None) + .map(|k| sp_io::hashing::blake2_256(k.serialize_compressed().as_ref()).into()) + .ok() + } + + fn parse_signature(signature_bytes: &Vec) -> Option { + ecdsa::Signature::from_slice(&signature_bytes[..]) + } + + fn get_evm_address_from_pubkey(pubkey: &[u8]) -> H160 { + H160::from(H256::from_slice(&keccak_256(pubkey))) + } +} diff --git a/precompiles/dispatch-lockdrop/src/mock.rs b/precompiles/dispatch-lockdrop/src/mock.rs new file mode 100644 index 0000000000..bb4f3b2402 --- /dev/null +++ b/precompiles/dispatch-lockdrop/src/mock.rs @@ -0,0 +1,261 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +use super::*; +use std::marker::PhantomData; + +use fp_evm::{IsPrecompileResult, Precompile}; +use frame_support::{construct_runtime, parameter_types, traits::ConstU64, weights::Weight}; +pub use pallet_evm::{ + AddressMapping, EnsureAddressNever, EnsureAddressRoot, PrecompileResult, PrecompileSet, +}; +use parity_scale_codec::Encode; +use sp_core::{keccak_256, H160, H256}; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, ConstU32, IdentityLookup}, + AccountId32, +}; + +use ethers::contract::{Eip712, EthAbiType}; +use ethers::prelude::transaction::eip712::Eip712; + +use frame_support::traits::Contains; + +use astar_primitives::evm::HashedDefaultMappings; +use astar_primitives::precompiles::DispatchFilterValidate; +pub type AccountId = AccountId32; +pub type Balance = u128; +pub type BlockNumber = u64; +pub type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +pub type Block = frame_system::mocking::MockBlock; +pub const PRECOMPILE_ADDRESS: H160 = H160::repeat_byte(0x7B); + +pub const ONE: u128 = 1_000_000_000_000_000_000; +pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); + +pub fn alice_secret() -> libsecp256k1::SecretKey { + libsecp256k1::SecretKey::parse(&keccak_256(b"Alice")).unwrap() +} +/// EIP712 Payload struct +#[derive(Eip712, EthAbiType, Clone)] +#[eip712( +name = "Astar EVM Claim", +version = "1", +chain_id = 1024, +// mock genisis hash +raw_salt = "0x4545454545454545454545454545454545454545454545454545454545454545" +)] +struct Claim { + substrate_address: ethers::core::types::Bytes, +} + +/// Build the signature payload for given native account and eth private key +pub fn get_evm_signature(who: &AccountId32, secret: &libsecp256k1::SecretKey) -> [u8; 65] { + // sign the payload + UnifiedAccounts::eth_sign_prehash( + &Claim { + substrate_address: who.encode().into(), + } + .encode_eip712() + .unwrap(), + secret, + ) +} + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, 0)); +} + +impl frame_system::Config for TestRuntime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type RuntimeCall = RuntimeCall; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId32; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +pub struct WhitelistedCalls(PhantomData); + +impl Contains for WhitelistedCalls { + fn contains(_t: &T) -> bool { + true + } +} + +#[derive(Debug, Clone, Copy)] +pub struct TestPrecompileSet(PhantomData); + +impl PrecompileSet for TestPrecompileSet +where + R: pallet_evm::Config, + DispatchLockdrop>>: + Precompile, +{ + fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { + match handle.code_address() { + a if a == PRECOMPILE_ADDRESS => Some(DispatchLockdrop::< + R, + DispatchFilterValidate>, + >::execute(handle)), + _ => None, + } + } + + fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: address == PRECOMPILE_ADDRESS, + extra_cost: 0, + } + } +} + +parameter_types! { + pub const ExistentialDeposit: u128 = 1; +} + +impl pallet_balances::Config for TestRuntime { + type MaxReserves = (); + type ReserveIdentifier = (); + type MaxLocks = (); + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type HoldIdentifier = (); + type FreezeIdentifier = (); + type MaxHolds = ConstU32<0>; + type MaxFreezes = ConstU32<0>; +} + +parameter_types! { + pub const MinimumPeriod: u64 = 5; +} + +impl pallet_timestamp::Config for TestRuntime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +parameter_types! { + pub const PrecompilesValue: TestPrecompileSet = + TestPrecompileSet(PhantomData); + pub WeightPerGas: Weight = Weight::from_parts(1, 0); +} + +pub type PrecompileCall = DispatchLockdropCall< + TestRuntime, + DispatchFilterValidate< + ::RuntimeCall, + WhitelistedCalls<::RuntimeCall>, + >, + ConstU32<8>, +>; + +impl pallet_evm::Config for TestRuntime { + type FeeCalculator = (); + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; + type WeightPerGas = WeightPerGas; + type CallOrigin = EnsureAddressRoot; + type WithdrawOrigin = EnsureAddressNever; + type AddressMapping = UnifiedAccounts; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type Runner = pallet_evm::runner::stack::Runner; + type PrecompilesType = TestPrecompileSet; + type PrecompilesValue = PrecompilesValue; + type Timestamp = Timestamp; + type ChainId = ChainId; + type OnChargeTransaction = (); + type BlockGasLimit = (); + type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; + type FindAuthor = (); + type OnCreate = (); + type WeightInfo = (); + type GasLimitPovSizeRatio = ConstU64<4>; +} + +parameter_types! { + // 2 storage items with value size 20 and 32 + pub const AccountMappingStorageFee: u128 = 0; + pub ChainId: u64 = 1024; +} + +impl pallet_unified_accounts::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type DefaultMappings = HashedDefaultMappings; + type ChainId = ChainId; + type AccountMappingStorageFee = AccountMappingStorageFee; + type WeightInfo = pallet_unified_accounts::weights::SubstrateWeight; +} + +// Configure a mock runtime to test the pallet. +construct_runtime!( + pub enum TestRuntime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Evm: pallet_evm, + UnifiedAccounts: pallet_unified_accounts, + Balances : pallet_balances, + Timestamp: pallet_timestamp, + } +); + +#[derive(Default)] +pub(crate) struct ExtBuilder; + +impl ExtBuilder { + pub(crate) fn build(self) -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::default() + .build_storage::() + .expect("Frame system builds valid default genesis config"); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} diff --git a/precompiles/dispatch-lockdrop/src/tests.rs b/precompiles/dispatch-lockdrop/src/tests.rs new file mode 100644 index 0000000000..258dd39840 --- /dev/null +++ b/precompiles/dispatch-lockdrop/src/tests.rs @@ -0,0 +1,80 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +use ethers::prelude::H256; +use frame_support::traits::Currency; +use sp_core::crypto::AccountId32; + +use crate::mock::*; + +use parity_scale_codec::Encode; +use precompile_utils::testing::*; +use sp_core::ecdsa; + +fn precompiles() -> TestPrecompileSet { + PrecompilesValue::get() +} + +#[test] +fn unify_lockdrop_account_works() { + ExtBuilder::default().build().execute_with(|| { + // Transfer balance to Alice + let call = RuntimeCall::Balances(pallet_balances::Call::transfer { + dest: ALICE, + value: 15 * ONE, + }); + // Sanity check - Alice holds no Balance + assert_eq!(Balances::free_balance(ALICE), 0); + + // Get Alice EVM address based on the Public Key + let alice_eth = UnifiedAccounts::eth_address(&alice_secret()); + // Get derived AccountId from the Blake2b hash of the compressed ECDSA Public key + let account_id = account_id(&alice_secret()); + // Fund this account (fund the lockdrop account) + let _ = Balances::deposit_creating(&account_id, ONE * 20); + // Sign the EIP712 payload + let sig = get_evm_signature(&account_id, &alice_secret()); + + precompiles() + .prepare_test( + alice_eth, + PRECOMPILE_ADDRESS, + PrecompileCall::dispatch_lockdrop_call { + call: call.encode().into(), + account_id: H256::from_slice(account_id.as_ref()), + signature: sig.into(), + }, + ) + .expect_no_logs() + .execute_returns(true); + + // Get Balance of ALICE in pallet balances + assert_eq!(Balances::free_balance(ALICE), 15 * ONE); + }); +} + +fn account_id(secret: &libsecp256k1::SecretKey) -> AccountId32 { + sp_io::hashing::blake2_256( + ecdsa::Public::from_full( + &libsecp256k1::PublicKey::from_secret_key(secret).serialize()[1..65], + ) + .unwrap() + .as_ref(), + ) + .into() +} diff --git a/runtime/local/Cargo.toml b/runtime/local/Cargo.toml index 96e6138426..119396c8c5 100644 --- a/runtime/local/Cargo.toml +++ b/runtime/local/Cargo.toml @@ -75,6 +75,7 @@ pallet-dapp-staking-v3 = { workspace = true } pallet-dapps-staking = { workspace = true } pallet-dynamic-evm-base-fee = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } +pallet-evm-precompile-dispatch-lockdrop = { workspace = true } pallet-evm-precompile-dapp-staking-v3 = { workspace = true } pallet-evm-precompile-rescue-lockdrop = { workspace = true } pallet-evm-precompile-sr25519 = { workspace = true } @@ -149,6 +150,7 @@ std = [ "pallet-evm-precompile-unified-accounts/std", "pallet-evm-precompile-xvm/std", "pallet-evm-precompile-rescue-lockdrop/std", + "pallet-evm-precompile-dispatch-lockdrop/std", "pallet-grandpa/std", "pallet-insecure-randomness-collective-flip/std", "pallet-preimage/std", diff --git a/runtime/local/src/precompiles.rs b/runtime/local/src/precompiles.rs index 5e73e99277..ffe45fdd62 100644 --- a/runtime/local/src/precompiles.rs +++ b/runtime/local/src/precompiles.rs @@ -21,11 +21,13 @@ use crate::{RuntimeCall, UnifiedAccounts}; use astar_primitives::precompiles::DispatchFilterValidate; use frame_support::{parameter_types, traits::Contains}; +use frame_support::pallet_prelude::ConstU32; use pallet_evm_precompile_assets_erc20::Erc20AssetsPrecompileSet; use pallet_evm_precompile_blake2::Blake2F; use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use pallet_evm_precompile_dapp_staking_v3::DappStakingV3Precompile; use pallet_evm_precompile_dispatch::Dispatch; +use pallet_evm_precompile_dispatch_lockdrop::DispatchLockdrop; use pallet_evm_precompile_ed25519::Ed25519Verify; use pallet_evm_precompile_modexp::Modexp; use pallet_evm_precompile_rescue_lockdrop::UnifyLockdropPrecompile; @@ -118,6 +120,12 @@ pub type LocalPrecompilesSetAt = ( (CallableByContract, CallableByPrecompile), >, PrecompileAt, UnifyLockdropPrecompile, ()>, + PrecompileAt< + AddressU64<20488>, + DispatchLockdrop, ConstU32<8>>, + // Not callable from smart contract nor precompiled, only EOA accounts + (), + >, ); pub type LocalPrecompiles = PrecompileSetBuilder< @@ -125,8 +133,8 @@ pub type LocalPrecompiles = PrecompileSetBuilder< ( // Skip precompiles if out of range. PrecompilesInRangeInclusive< - // We take range as last precompile index, UPDATE this once new prcompile is added - (AddressU64<1>, AddressU64<20486>), + // We take range as last precompile index, UPDATE this once new precompile is added + (AddressU64<1>, AddressU64<20488>), LocalPrecompilesSetAt, >, // Prefixed precompile sets (XC20) From 06ab9f5ce4df8178addf35e0d23fc35505fcd3ee Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Tue, 23 Jan 2024 17:23:38 +0100 Subject: [PATCH 05/20] deleted rescue lockdrop --- Cargo.lock | 26 -- Cargo.toml | 1 - precompiles/dispatch-lockdrop/Cargo.toml | 46 ++-- precompiles/dispatch-lockdrop/src/lib.rs | 5 +- precompiles/rescue-lockdrop/Cargo.toml | 55 ---- .../rescue-lockdrop/RescueLockdrop.sol | 14 -- precompiles/rescue-lockdrop/src/lib.rs | 157 ------------ precompiles/rescue-lockdrop/src/mock.rs | 236 ------------------ precompiles/rescue-lockdrop/src/tests.rs | 103 -------- runtime/local/Cargo.toml | 4 +- runtime/local/src/precompiles.rs | 8 +- runtime/shibuya/Cargo.toml | 2 - runtime/shibuya/src/precompiles.rs | 2 - 13 files changed, 30 insertions(+), 629 deletions(-) delete mode 100644 precompiles/rescue-lockdrop/Cargo.toml delete mode 100644 precompiles/rescue-lockdrop/RescueLockdrop.sol delete mode 100644 precompiles/rescue-lockdrop/src/lib.rs delete mode 100644 precompiles/rescue-lockdrop/src/mock.rs delete mode 100644 precompiles/rescue-lockdrop/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index a1095a2c5e..af3149754d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6077,7 +6077,6 @@ dependencies = [ "pallet-evm-precompile-dispatch-lockdrop", "pallet-evm-precompile-ed25519", "pallet-evm-precompile-modexp", - "pallet-evm-precompile-rescue-lockdrop", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", "pallet-evm-precompile-sr25519", @@ -8080,30 +8079,6 @@ dependencies = [ "num", ] -[[package]] -name = "pallet-evm-precompile-rescue-lockdrop" -version = "0.1.0" -dependencies = [ - "astar-primitives", - "ethers", - "fp-evm", - "frame-support", - "frame-system", - "libsecp256k1", - "log", - "pallet-balances", - "pallet-evm", - "pallet-timestamp", - "pallet-unified-accounts", - "parity-scale-codec", - "precompile-utils", - "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", -] - [[package]] name = "pallet-evm-precompile-sha3fips" version = "2.0.0-dev" @@ -13295,7 +13270,6 @@ dependencies = [ "pallet-evm-precompile-dispatch", "pallet-evm-precompile-ed25519", "pallet-evm-precompile-modexp", - "pallet-evm-precompile-rescue-lockdrop", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", "pallet-evm-precompile-sr25519", diff --git a/Cargo.toml b/Cargo.toml index e6d32066b4..bd32202d60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -299,7 +299,6 @@ pallet-evm-precompile-xvm = { path = "./precompiles/xvm", default-features = fal pallet-evm-precompile-dapps-staking = { path = "./precompiles/dapps-staking", default-features = false } pallet-evm-precompile-dapp-staking-v3 = { path = "./precompiles/dapp-staking-v3", default-features = false } pallet-evm-precompile-unified-accounts = { path = "./precompiles/unified-accounts", default-features = false } -pallet-evm-precompile-rescue-lockdrop = { path = "./precompiles/rescue-lockdrop", default-features = false } pallet-evm-precompile-dispatch-lockdrop = { path = "./precompiles/dispatch-lockdrop", default-features = false } pallet-chain-extension-xvm = { path = "./chain-extensions/xvm", default-features = false } diff --git a/precompiles/dispatch-lockdrop/Cargo.toml b/precompiles/dispatch-lockdrop/Cargo.toml index acb30deebc..cf0b8f76d3 100644 --- a/precompiles/dispatch-lockdrop/Cargo.toml +++ b/precompiles/dispatch-lockdrop/Cargo.toml @@ -8,46 +8,46 @@ homepage.workspace = true repository.workspace = true [dependencies] -frame-support = { workspace = true } fp-evm = { workspace = true } -pallet-evm = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } libsecp256k1 = { workspace = true, features = ["hmac", "static-context"] } log = { workspace = true } +pallet-evm = { workspace = true } +pallet-evm-precompile-dispatch = { workspace = true } +pallet-unified-accounts = { workspace = true } precompile-utils = { workspace = true } -frame-system = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-std = { workspace = true } -pallet-unified-accounts = { workspace = true } -pallet-evm-precompile-dispatch = { workspace = true } [dev-dependencies] +astar-primitives = { workspace = true } ethers = { workspace = true } -parity-scale-codec = { package = "parity-scale-codec", workspace = true } -scale-info = { workspace = true } frame-system = { workspace = true } pallet-balances = { workspace = true } pallet-timestamp = { workspace = true } +parity-scale-codec = { package = "parity-scale-codec", workspace = true } +precompile-utils = { workspace = true, features = ["testing"] } +scale-info = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } -sp-std = { workspace = true } sp-runtime = { workspace = true } -precompile-utils = { workspace = true, features = ["testing"] } -astar-primitives = { workspace = true } +sp-std = { workspace = true } [features] default = ["std"] std = [ - "pallet-unified-accounts/std", - "fp-evm/std", - "frame-support/std", - "frame-system/std", - "pallet-evm/std", - "precompile-utils/std", - "sp-core/std", - "sp-std/std", - "sp-io/std", - "libsecp256k1/std", - "pallet-evm/std", - "pallet-evm-precompile-dispatch/std", -] \ No newline at end of file + "pallet-unified-accounts/std", + "fp-evm/std", + "frame-support/std", + "frame-system/std", + "pallet-evm/std", + "precompile-utils/std", + "sp-core/std", + "sp-std/std", + "sp-io/std", + "libsecp256k1/std", + "pallet-evm/std", + "pallet-evm-precompile-dispatch/std", +] diff --git a/precompiles/dispatch-lockdrop/src/lib.rs b/precompiles/dispatch-lockdrop/src/lib.rs index 4956c15357..de21df1f6c 100644 --- a/precompiles/dispatch-lockdrop/src/lib.rs +++ b/precompiles/dispatch-lockdrop/src/lib.rs @@ -30,6 +30,7 @@ use frame_support::{ }; use frame_system::Config; use pallet_evm::GasWeightMapping; +use pallet_evm_precompile_dispatch::DispatchValidateT; use precompile_utils::prelude::{BoundedBytes, UnboundedBytes}; use precompile_utils::EvmResult; use sp_core::ecdsa; @@ -37,7 +38,6 @@ use sp_core::ecdsa::Signature; use sp_core::{crypto::AccountId32, H160, H256}; use sp_io::hashing::keccak_256; use sp_std::vec::Vec; -use pallet_evm_precompile_dispatch::DispatchValidateT; #[cfg(test)] mod mock; @@ -63,7 +63,8 @@ where Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, ::AccountId: IsType, ::AccountId: From<[u8; 32]>, - DispatchValidator: DispatchValidateT<::AccountId, ::RuntimeCall>, + DispatchValidator: + DispatchValidateT<::AccountId, ::RuntimeCall>, DecodeLimit: Get, { #[precompile::public("dispatch_lockdrop_call(bytes,bytes32,bytes)")] diff --git a/precompiles/rescue-lockdrop/Cargo.toml b/precompiles/rescue-lockdrop/Cargo.toml deleted file mode 100644 index 3b9b6a7f20..0000000000 --- a/precompiles/rescue-lockdrop/Cargo.toml +++ /dev/null @@ -1,55 +0,0 @@ -[package] -name = "pallet-evm-precompile-rescue-lockdrop" -description = "Evm Precompile to rescue lockdrop accounts" -version = "0.1.0" -authors.workspace = true -edition.workspace = true -homepage.workspace = true -repository.workspace = true - -[dependencies] -libsecp256k1 = { workspace = true, features = ["hmac", "static-context"] } -log = { workspace = true } -precompile-utils = { workspace = true } - -# Substrate -frame-support = { workspace = true } -frame-system = { workspace = true } -parity-scale-codec = { workspace = true, features = ["max-encoded-len"] } -sp-core = { workspace = true } -sp-io = { workspace = true } -sp-runtime = { workspace = true } -sp-std = { workspace = true } - -# Frontier -fp-evm = { workspace = true } -pallet-evm = { workspace = true } -pallet-unified-accounts = { workspace = true } - -# Astar -astar-primitives = { workspace = true } - -[dev-dependencies] -ethers = { workspace = true } -pallet-balances = { workspace = true, features = ["std"] } -pallet-timestamp = { workspace = true } -precompile-utils = { workspace = true, features = ["testing"] } -scale-info = { workspace = true } - -[features] -default = ["std"] -std = [ - "parity-scale-codec/std", - "pallet-unified-accounts/std", - "fp-evm/std", - "frame-support/std", - "frame-system/std", - "pallet-evm/std", - "precompile-utils/std", - "sp-core/std", - "sp-std/std", - "sp-io/std", - "sp-runtime/std", - "astar-primitives/std", - "libsecp256k1/std", -] diff --git a/precompiles/rescue-lockdrop/RescueLockdrop.sol b/precompiles/rescue-lockdrop/RescueLockdrop.sol deleted file mode 100644 index 363648d3f4..0000000000 --- a/precompiles/rescue-lockdrop/RescueLockdrop.sol +++ /dev/null @@ -1,14 +0,0 @@ -pragma solidity ^0.8.0; - -/** - * @title Rescue Lockdrop interface. - */ - -/// Interface to the rescue lockdrop precompiled contract -/// Predeployed at the address 0x0000000000000000000000000000000000005007 -interface RescueLockdrop { - function claim_lock_drop_account( - bytes32 accountId, - bytes signature - ) external returns (bool); -} diff --git a/precompiles/rescue-lockdrop/src/lib.rs b/precompiles/rescue-lockdrop/src/lib.rs deleted file mode 100644 index 1c50018991..0000000000 --- a/precompiles/rescue-lockdrop/src/lib.rs +++ /dev/null @@ -1,157 +0,0 @@ -// This file is part of Astar. - -// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later - -// Astar 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. - -// Astar 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 Astar. If not, see . - -#![cfg_attr(not(feature = "std"), no_std)] - -use core::marker::PhantomData; -use fp_evm::PrecompileHandle; -use frame_support::pallet_prelude::IsType; -use frame_support::{ - dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, - traits::ConstU32, -}; -use frame_system::Config; -use precompile_utils::prelude::BoundedBytes; -use precompile_utils::prelude::RuntimeHelper; -use precompile_utils::EvmResult; -use sp_core::ecdsa; -use sp_core::ecdsa::Signature; -use sp_core::{crypto::AccountId32, H160, H256}; -use sp_io::hashing::keccak_256; -use sp_std::vec::Vec; - -#[cfg(test)] -mod mock; -#[cfg(test)] -mod tests; - -// ECDSA signature bytes -type ECDSASignatureBytes = ConstU32<65>; - -/// A precompile to unify lock drop account. -pub struct UnifyLockdropPrecompile(PhantomData); - -#[precompile_utils::precompile] -impl UnifyLockdropPrecompile -where - R: pallet_evm::Config + pallet_unified_accounts::Config, - ::RuntimeOrigin: From>, - R::RuntimeCall: Dispatchable + GetDispatchInfo, - R::RuntimeCall: From>, - ::AccountId: IsType, - ::AccountId: From<[u8; 32]>, -{ - #[precompile::public("claim_lock_drop_account(bytes32,bytes)")] - fn claim_lock_drop_account( - handle: &mut impl PrecompileHandle, - account_id: H256, - signature: BoundedBytes, - ) -> EvmResult { - log::trace!( - target: "rescue-lockdrop-precompile:claim_lock_drop_account", - "raw arguments: account_id: {:?}, signature: {:?}", - account_id, - signature - ); - - let caller = handle.context().caller.into(); - let signature_bytes: Vec = signature.into(); - let account_id = AccountId32::new(account_id.into()).into(); - - // 1. Recover the ECDSA Public key from the signature - let signature_opt = match Self::parse_signature(&signature_bytes) { - Some(s) => s, - None => { - log::trace!( - target: "rescue-lockdrop-precompile:claim_lock_drop_account", - "Error: could not parse signature" - ); - return Ok(false); - } - }; - let pubkey = match >::recover_pubkey( - &account_id, - signature_opt.as_ref(), - ) { - Some(k) => k, - None => { - log::trace!( - target: "rescue-lockdrop-precompile:claim_lock_drop_account", - "Error: could not recover pubkey from signature" - ); - return Ok(false); - } - }; - - // 2. Ensure that the caller matches the recovered EVM address from the signature - if caller != Self::get_evm_address_from_pubkey(&pubkey) { - log::trace!( - target: "rescue-lockdrop-precompile:claim_lock_drop_account", - "Error: caller does not match calculated EVM address" - ); - return Ok(false); - } - - // 3. Derive the AccountId from the ECDSA compressed Public key - let origin = match Self::get_account_id_from_pubkey(pubkey) { - Some(a) => a, - None => { - log::trace!( - target: "rescue-lockdrop-precompile:claim_lock_drop_account", - "Error: could not derive AccountId from pubkey" - ); - return Ok(false); - } - }; - - // 4. Call to Claim EVM address - it will populate the mapping between the EVM address and the derived AccountId - // As sufficient checks has been done: - // - the caller matches the recovered EVM address from the signature - // - the AccountId is derived from the recovered ECDSA Public key - let call = pallet_unified_accounts::Call::::claim_evm_address { - evm_address: caller, - signature: signature_opt.into(), - }; - - match RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call) { - Ok(_) => Ok(true), - Err(e) => { - log::trace!( - target: "rescue-lockdrop-precompile:claim_lock_drop_account", - "Error: {:?}", - e - ); - Ok(false) - } - } - } - - fn get_account_id_from_pubkey(pubkey: [u8; 64]) -> Option<::AccountId> { - libsecp256k1::PublicKey::parse_slice(&pubkey, None) - .map(|k| sp_io::hashing::blake2_256(k.serialize_compressed().as_ref()).into()) - .ok() - } - - fn parse_signature(signature_bytes: &Vec) -> Option { - ecdsa::Signature::from_slice(&signature_bytes[..]) - } - - fn get_evm_address_from_pubkey(pubkey: &[u8]) -> H160 { - H160::from(H256::from_slice(&keccak_256(pubkey))) - } -} diff --git a/precompiles/rescue-lockdrop/src/mock.rs b/precompiles/rescue-lockdrop/src/mock.rs deleted file mode 100644 index 37cd27cbd8..0000000000 --- a/precompiles/rescue-lockdrop/src/mock.rs +++ /dev/null @@ -1,236 +0,0 @@ -// This file is part of Astar. - -// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later - -// Astar 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. - -// Astar 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 Astar. If not, see . - -use super::*; - -use fp_evm::{IsPrecompileResult, Precompile}; -use frame_support::{construct_runtime, parameter_types, traits::ConstU64, weights::Weight}; -pub use pallet_evm::{ - AddressMapping, EnsureAddressNever, EnsureAddressRoot, PrecompileResult, PrecompileSet, -}; -use parity_scale_codec::Encode; -use sp_core::{keccak_256, H160, H256}; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, ConstU32, IdentityLookup}, - AccountId32, -}; - -use ethers::contract::{Eip712, EthAbiType}; -use ethers::prelude::transaction::eip712::Eip712; - -use astar_primitives::evm::HashedDefaultMappings; -pub type AccountId = AccountId32; -pub type Balance = u128; -pub type BlockNumber = u64; -pub type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -pub type Block = frame_system::mocking::MockBlock; - -pub const PRECOMPILE_ADDRESS: H160 = H160::repeat_byte(0x7B); - -pub fn alice_secret() -> libsecp256k1::SecretKey { - libsecp256k1::SecretKey::parse(&keccak_256(b"Alice")).unwrap() -} -/// EIP712 Payload struct -#[derive(Eip712, EthAbiType, Clone)] -#[eip712( -name = "Astar EVM Claim", -version = "1", -chain_id = 1024, -// mock genisis hash -raw_salt = "0x4545454545454545454545454545454545454545454545454545454545454545" -)] -struct Claim { - substrate_address: ethers::core::types::Bytes, -} - -/// Build the signature payload for given native account and eth private key -pub fn get_evm_signature(who: &AccountId32, secret: &libsecp256k1::SecretKey) -> [u8; 65] { - // sign the payload - UnifiedAccounts::eth_sign_prehash( - &Claim { - substrate_address: who.encode().into(), - } - .encode_eip712() - .unwrap(), - secret, - ) -} - -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, 0)); -} - -impl frame_system::Config for TestRuntime { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type RuntimeCall = RuntimeCall; - type BlockNumber = BlockNumber; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId32; - type Lookup = IdentityLookup; - type Header = Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; -} - -#[derive(Debug, Clone, Copy)] -pub struct TestPrecompileSet(PhantomData); - -impl PrecompileSet for TestPrecompileSet -where - R: pallet_evm::Config, - UnifyLockdropPrecompile: Precompile, -{ - fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { - match handle.code_address() { - a if a == PRECOMPILE_ADDRESS => Some(UnifyLockdropPrecompile::::execute(handle)), - _ => None, - } - } - - fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { - IsPrecompileResult::Answer { - is_precompile: address == PRECOMPILE_ADDRESS, - extra_cost: 0, - } - } -} - -parameter_types! { - pub const ExistentialDeposit: u128 = 1; -} - -impl pallet_balances::Config for TestRuntime { - type MaxReserves = (); - type ReserveIdentifier = (); - type MaxLocks = (); - type Balance = Balance; - type RuntimeEvent = RuntimeEvent; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type WeightInfo = (); - type HoldIdentifier = (); - type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; - type MaxFreezes = ConstU32<0>; -} - -parameter_types! { - pub const MinimumPeriod: u64 = 5; -} - -impl pallet_timestamp::Config for TestRuntime { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; - type WeightInfo = (); -} - -parameter_types! { - pub const PrecompilesValue: TestPrecompileSet = - TestPrecompileSet(PhantomData); - pub WeightPerGas: Weight = Weight::from_parts(1, 0); -} - -pub type PrecompileCall = UnifyLockdropPrecompileCall; - -impl pallet_evm::Config for TestRuntime { - type FeeCalculator = (); - type GasWeightMapping = pallet_evm::FixedGasWeightMapping; - type WeightPerGas = WeightPerGas; - type CallOrigin = EnsureAddressRoot; - type WithdrawOrigin = EnsureAddressNever; - type AddressMapping = UnifiedAccounts; - type Currency = Balances; - type RuntimeEvent = RuntimeEvent; - type Runner = pallet_evm::runner::stack::Runner; - type PrecompilesType = TestPrecompileSet; - type PrecompilesValue = PrecompilesValue; - type Timestamp = Timestamp; - type ChainId = ChainId; - type OnChargeTransaction = (); - type BlockGasLimit = (); - type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; - type FindAuthor = (); - type OnCreate = (); - type WeightInfo = (); - type GasLimitPovSizeRatio = ConstU64<4>; -} - -parameter_types! { - // 2 storage items with value size 20 and 32 - pub const AccountMappingStorageFee: u128 = 0; - pub ChainId: u64 = 1024; -} - -impl pallet_unified_accounts::Config for TestRuntime { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type DefaultMappings = HashedDefaultMappings; - type ChainId = ChainId; - type AccountMappingStorageFee = AccountMappingStorageFee; - type WeightInfo = pallet_unified_accounts::weights::SubstrateWeight; -} - -// Configure a mock runtime to test the pallet. -construct_runtime!( - pub enum TestRuntime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system, - Evm: pallet_evm, - UnifiedAccounts: pallet_unified_accounts, - Balances : pallet_balances, - Timestamp: pallet_timestamp, - } -); - -#[derive(Default)] -pub(crate) struct ExtBuilder; - -impl ExtBuilder { - pub(crate) fn build(self) -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::default() - .build_storage::() - .expect("Frame system builds valid default genesis config"); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext - } -} diff --git a/precompiles/rescue-lockdrop/src/tests.rs b/precompiles/rescue-lockdrop/src/tests.rs deleted file mode 100644 index 612024658f..0000000000 --- a/precompiles/rescue-lockdrop/src/tests.rs +++ /dev/null @@ -1,103 +0,0 @@ -// This file is part of Astar. - -// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later - -// Astar 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. - -// Astar 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 Astar. If not, see . - -use ethers::prelude::H256; -use sp_core::crypto::AccountId32; - -use crate::mock::*; - -use astar_primitives::evm::UnifiedAddressMapper; -use precompile_utils::testing::*; -use sp_core::ecdsa; - -fn precompiles() -> TestPrecompileSet { - PrecompilesValue::get() -} - -#[test] -fn unify_lockdrop_account_works() { - ExtBuilder::default().build().execute_with(|| { - // Get Alice EVM address based on the Public Key - let alice_eth = UnifiedAccounts::eth_address(&alice_secret()); - // Get derived AccountId from the Blake2b hash of the compressed ECDSA Public key - let account_id = account_id(&alice_secret()); - // Sign the EIP712 payload to Claim EVM address - let sig = get_evm_signature(&account_id, &alice_secret()); - - precompiles() - .prepare_test( - alice_eth, - PRECOMPILE_ADDRESS, - PrecompileCall::claim_lock_drop_account { - account_id: H256::from_slice(account_id.as_ref()), - signature: sig.into(), - }, - ) - .expect_no_logs() - .execute_returns(true); - - // Ensure that the AccountId mapped with Alice EVM account is the AccountId from the Blake2b hash of the compressed ECDSA Public key - let alice_account_id = UnifiedAccounts::into_account_id(alice_eth); - assert_eq!(alice_account_id, account_id); - }); -} - -#[test] -fn wrong_caller_cannot_unify_derived_address() { - ExtBuilder::default().build().execute_with(|| { - // Get Alice EVM address based on the Public Key - let alice_eth = UnifiedAccounts::eth_address(&alice_secret()); - // Get derived AccountId from the Blake2b hash of the compressed ECDSA Public key - let account_id = account_id(&alice_secret()); - // Sign the EIP712 payload to Claim EVM address - let sig = get_evm_signature(&account_id, &alice_secret()); - - precompiles() - .prepare_test( - Bob, - PRECOMPILE_ADDRESS, - PrecompileCall::claim_lock_drop_account { - account_id: H256::from_slice(account_id.as_ref()), - signature: sig.into(), - }, - ) - .expect_no_logs() - .execute_returns(false); - - // Ensure that the AccountId mapped with Alice EVM account is the default AccountId (not the AccountId from the Blake2b hash of the compressed ECDSA Public key) - // This means account has not been mapped - let alice_account_id = UnifiedAccounts::into_account_id(alice_eth); - let alice_default_account_id = - ::DefaultMappings::to_default_account_id( - &alice_eth, - ); - - assert_eq!(alice_account_id, alice_default_account_id); - }); -} - -fn account_id(secret: &libsecp256k1::SecretKey) -> AccountId32 { - sp_io::hashing::blake2_256( - ecdsa::Public::from_full( - &libsecp256k1::PublicKey::from_secret_key(secret).serialize()[1..65], - ) - .unwrap() - .as_ref(), - ) - .into() -} diff --git a/runtime/local/Cargo.toml b/runtime/local/Cargo.toml index 119396c8c5..eee3b06e59 100644 --- a/runtime/local/Cargo.toml +++ b/runtime/local/Cargo.toml @@ -75,9 +75,8 @@ pallet-dapp-staking-v3 = { workspace = true } pallet-dapps-staking = { workspace = true } pallet-dynamic-evm-base-fee = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } -pallet-evm-precompile-dispatch-lockdrop = { workspace = true } pallet-evm-precompile-dapp-staking-v3 = { workspace = true } -pallet-evm-precompile-rescue-lockdrop = { workspace = true } +pallet-evm-precompile-dispatch-lockdrop = { workspace = true } pallet-evm-precompile-sr25519 = { workspace = true } pallet-evm-precompile-substrate-ecdsa = { workspace = true } pallet-evm-precompile-unified-accounts = { workspace = true } @@ -149,7 +148,6 @@ std = [ "pallet-evm-precompile-substrate-ecdsa/std", "pallet-evm-precompile-unified-accounts/std", "pallet-evm-precompile-xvm/std", - "pallet-evm-precompile-rescue-lockdrop/std", "pallet-evm-precompile-dispatch-lockdrop/std", "pallet-grandpa/std", "pallet-insecure-randomness-collective-flip/std", diff --git a/runtime/local/src/precompiles.rs b/runtime/local/src/precompiles.rs index ffe45fdd62..136852f80e 100644 --- a/runtime/local/src/precompiles.rs +++ b/runtime/local/src/precompiles.rs @@ -20,8 +20,8 @@ use crate::{RuntimeCall, UnifiedAccounts}; use astar_primitives::precompiles::DispatchFilterValidate; -use frame_support::{parameter_types, traits::Contains}; use frame_support::pallet_prelude::ConstU32; +use frame_support::{parameter_types, traits::Contains}; use pallet_evm_precompile_assets_erc20::Erc20AssetsPrecompileSet; use pallet_evm_precompile_blake2::Blake2F; use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; @@ -30,7 +30,6 @@ use pallet_evm_precompile_dispatch::Dispatch; use pallet_evm_precompile_dispatch_lockdrop::DispatchLockdrop; use pallet_evm_precompile_ed25519::Ed25519Verify; use pallet_evm_precompile_modexp::Modexp; -use pallet_evm_precompile_rescue_lockdrop::UnifyLockdropPrecompile; use pallet_evm_precompile_sha3fips::Sha3FIPS256; use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; use pallet_evm_precompile_sr25519::Sr25519Precompile; @@ -119,9 +118,8 @@ pub type LocalPrecompilesSetAt = ( UnifiedAccountsPrecompile, (CallableByContract, CallableByPrecompile), >, - PrecompileAt, UnifyLockdropPrecompile, ()>, PrecompileAt< - AddressU64<20488>, + AddressU64<20487>, DispatchLockdrop, ConstU32<8>>, // Not callable from smart contract nor precompiled, only EOA accounts (), @@ -134,7 +132,7 @@ pub type LocalPrecompiles = PrecompileSetBuilder< // Skip precompiles if out of range. PrecompilesInRangeInclusive< // We take range as last precompile index, UPDATE this once new precompile is added - (AddressU64<1>, AddressU64<20488>), + (AddressU64<1>, AddressU64<20487>), LocalPrecompilesSetAt, >, // Prefixed precompile sets (XC20) diff --git a/runtime/shibuya/Cargo.toml b/runtime/shibuya/Cargo.toml index b71a169bf5..4eafb194fa 100644 --- a/runtime/shibuya/Cargo.toml +++ b/runtime/shibuya/Cargo.toml @@ -106,7 +106,6 @@ pallet-dynamic-evm-base-fee = { workspace = true } pallet-ethereum-checked = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } pallet-evm-precompile-dapp-staking-v3 = { workspace = true } -pallet-evm-precompile-rescue-lockdrop = { workspace = true } pallet-evm-precompile-sr25519 = { workspace = true } pallet-evm-precompile-substrate-ecdsa = { workspace = true } pallet-evm-precompile-unified-accounts = { workspace = true } @@ -230,7 +229,6 @@ std = [ "cumulus-pallet-dmp-queue/std", "cumulus-pallet-xcmp-queue/std", "cumulus-pallet-xcm/std", - "pallet-evm-precompile-rescue-lockdrop/std", "pallet-collator-selection/std", "frame-benchmarking/std", "frame-try-runtime/std", diff --git a/runtime/shibuya/src/precompiles.rs b/runtime/shibuya/src/precompiles.rs index d910e7059b..c9f34a9a9d 100644 --- a/runtime/shibuya/src/precompiles.rs +++ b/runtime/shibuya/src/precompiles.rs @@ -28,7 +28,6 @@ use pallet_evm_precompile_dapp_staking_v3::DappStakingV3Precompile; use pallet_evm_precompile_dispatch::Dispatch; use pallet_evm_precompile_ed25519::Ed25519Verify; use pallet_evm_precompile_modexp::Modexp; -use pallet_evm_precompile_rescue_lockdrop::UnifyLockdropPrecompile; use pallet_evm_precompile_sha3fips::Sha3FIPS256; use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; use pallet_evm_precompile_sr25519::Sr25519Precompile; @@ -126,7 +125,6 @@ pub type ShibuyaPrecompilesSetAt = ( UnifiedAccountsPrecompile, (CallableByContract, CallableByPrecompile), >, - PrecompileAt, UnifyLockdropPrecompile, ()>, ); pub type ShibuyaPrecompiles = PrecompileSetBuilder< From 8532480b443e4632623756894042795a56ab1814 Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Fri, 26 Jan 2024 13:51:17 +0100 Subject: [PATCH 06/20] Added tests --- Cargo.lock | 1 + pallets/unified-accounts/src/lib.rs | 10 +-- pallets/unified-accounts/src/tests.rs | 4 +- precompiles/dispatch-lockdrop/Cargo.toml | 4 +- precompiles/dispatch-lockdrop/src/lib.rs | 43 ++++++++++++- precompiles/dispatch-lockdrop/src/mock.rs | 28 ++++----- precompiles/dispatch-lockdrop/src/tests.rs | 72 +++++++++++++++++++++- runtime/shibuya/Cargo.toml | 2 + runtime/shibuya/src/precompiles.rs | 8 +++ 9 files changed, 146 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af3149754d..7bbbff568d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13268,6 +13268,7 @@ dependencies = [ "pallet-evm-precompile-bn128", "pallet-evm-precompile-dapp-staking-v3", "pallet-evm-precompile-dispatch", + "pallet-evm-precompile-dispatch-lockdrop", "pallet-evm-precompile-ed25519", "pallet-evm-precompile-modexp", "pallet-evm-precompile-sha3fips", diff --git a/pallets/unified-accounts/src/lib.rs b/pallets/unified-accounts/src/lib.rs index a88f123cee..521ebeb75b 100644 --- a/pallets/unified-accounts/src/lib.rs +++ b/pallets/unified-accounts/src/lib.rs @@ -192,7 +192,8 @@ pub mod pallet { ); // recover evm address from signature - let address = Self::verify_signature(&who, &signature) + let payload_hash = Self::build_signing_payload(&who); + let address = Self::verify_signature(&signature, payload_hash) .ok_or(Error::::UnexpectedSignatureFormat)?; ensure!(evm_address == address, Error::::InvalidSignature); @@ -295,13 +296,12 @@ impl Pallet { keccak_256(&payload) } - pub fn recover_pubkey(who: &T::AccountId, sig: &EvmSignature) -> Option<[u8; 64]> { - let payload_hash = Self::build_signing_payload(who); + pub fn recover_pubkey(sig: &EvmSignature, payload_hash: [u8; 32]) -> Option<[u8; 64]> { sp_io::crypto::secp256k1_ecdsa_recover(sig, &payload_hash).ok() } - pub fn verify_signature(who: &T::AccountId, sig: &EvmSignature) -> Option { - Self::recover_pubkey(who, sig) + pub fn verify_signature(sig: &EvmSignature, payload_hash: [u8; 32]) -> Option { + Self::recover_pubkey(sig, payload_hash) .map(|pubkey| H160::from(H256::from_slice(&keccak_256(&pubkey)))) } diff --git a/pallets/unified-accounts/src/tests.rs b/pallets/unified-accounts/src/tests.rs index 7f766a3431..b5225a2814 100644 --- a/pallets/unified-accounts/src/tests.rs +++ b/pallets/unified-accounts/src/tests.rs @@ -83,7 +83,7 @@ fn eip712_signature_verify_works() { let sig = UnifiedAccounts::eth_sign_prehash(&claim_hash, &alice_secret()); assert_eq!( Some(UnifiedAccounts::eth_address(&alice_secret())), - UnifiedAccounts::verify_signature(&ALICE, &sig), + UnifiedAccounts::verify_signature(&sig, claim_hash), "signature verification should work" ); }); @@ -422,7 +422,7 @@ fn recover_public_key_works() { // assert public key recovery works assert_eq!( expected_pubkey, - UnifiedAccounts::recover_pubkey(&ALICE, &signature) + UnifiedAccounts::recover_pubkey(&signature, claim_hash) .unwrap() .as_ref(), "recover public key should work" diff --git a/precompiles/dispatch-lockdrop/Cargo.toml b/precompiles/dispatch-lockdrop/Cargo.toml index cf0b8f76d3..313da295c8 100644 --- a/precompiles/dispatch-lockdrop/Cargo.toml +++ b/precompiles/dispatch-lockdrop/Cargo.toml @@ -16,9 +16,11 @@ log = { workspace = true } pallet-evm = { workspace = true } pallet-evm-precompile-dispatch = { workspace = true } pallet-unified-accounts = { workspace = true } +parity-scale-codec = { workspace = true } precompile-utils = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } +sp-runtime = { workspace = true } sp-std = { workspace = true } [dev-dependencies] @@ -27,7 +29,6 @@ ethers = { workspace = true } frame-system = { workspace = true } pallet-balances = { workspace = true } pallet-timestamp = { workspace = true } -parity-scale-codec = { package = "parity-scale-codec", workspace = true } precompile-utils = { workspace = true, features = ["testing"] } scale-info = { workspace = true } sp-core = { workspace = true } @@ -47,6 +48,7 @@ std = [ "sp-core/std", "sp-std/std", "sp-io/std", + "sp-runtime/std", "libsecp256k1/std", "pallet-evm/std", "pallet-evm-precompile-dispatch/std", diff --git a/precompiles/dispatch-lockdrop/src/lib.rs b/precompiles/dispatch-lockdrop/src/lib.rs index de21df1f6c..ab28ee1bdf 100644 --- a/precompiles/dispatch-lockdrop/src/lib.rs +++ b/precompiles/dispatch-lockdrop/src/lib.rs @@ -31,12 +31,14 @@ use frame_support::{ use frame_system::Config; use pallet_evm::GasWeightMapping; use pallet_evm_precompile_dispatch::DispatchValidateT; +use parity_scale_codec::Encode; use precompile_utils::prelude::{BoundedBytes, UnboundedBytes}; -use precompile_utils::EvmResult; -use sp_core::ecdsa; +use precompile_utils::{keccak256, EvmResult}; use sp_core::ecdsa::Signature; use sp_core::{crypto::AccountId32, H160, H256}; +use sp_core::{ecdsa, U256}; use sp_io::hashing::keccak_256; +use sp_runtime::traits::Zero; use sp_std::vec::Vec; #[cfg(test)] @@ -127,9 +129,10 @@ where return Ok(false); } }; + let payload_hash = Self::build_signing_payload(&account_id); let pubkey = match >::recover_pubkey( - &account_id, signature_opt.as_ref(), + payload_hash, ) { Some(k) => k, None => { @@ -217,4 +220,38 @@ where fn get_evm_address_from_pubkey(pubkey: &[u8]) -> H160 { H160::from(H256::from_slice(&keccak_256(pubkey))) } + + fn build_signing_payload(who: &::AccountId) -> [u8; 32] { + let domain_separator = Self::build_domain_separator(); + let args_hash = Self::build_args_hash(who); + + let mut payload = b"\x19\x01".to_vec(); + payload.extend_from_slice(&domain_separator); + payload.extend_from_slice(&args_hash); + keccak_256(&payload) + } + + fn build_domain_separator() -> [u8; 32] { + let mut domain = + keccak256!("EIP712Domain(string name,string version,uint256 chainId,bytes32 salt)") + .to_vec(); + domain.extend_from_slice(&keccak256!("Astar EVM dispatch")); // name + domain.extend_from_slice(&keccak256!("1")); // version + domain.extend_from_slice( + &(<[u8; 32]>::from(U256::from( + ::ChainId::get(), + ))), + ); // chain id + domain.extend_from_slice( + frame_system::Pallet::::block_hash(::BlockNumber::zero()) + .as_ref(), + ); // genesis block hash + keccak_256(domain.as_slice()) + } + + fn build_args_hash(account: &::AccountId) -> [u8; 32] { + let mut args_hash = keccak256!("Dispatch(bytes substrateAddress)").to_vec(); + args_hash.extend_from_slice(&keccak_256(&account.encode())); + keccak_256(args_hash.as_slice()) + } } diff --git a/precompiles/dispatch-lockdrop/src/mock.rs b/precompiles/dispatch-lockdrop/src/mock.rs index bb4f3b2402..7d7cd9ce7e 100644 --- a/precompiles/dispatch-lockdrop/src/mock.rs +++ b/precompiles/dispatch-lockdrop/src/mock.rs @@ -48,6 +48,7 @@ pub const PRECOMPILE_ADDRESS: H160 = H160::repeat_byte(0x7B); pub const ONE: u128 = 1_000_000_000_000_000_000; pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); +pub const DUMMY: AccountId32 = AccountId32::new([2u8; 32]); pub fn alice_secret() -> libsecp256k1::SecretKey { libsecp256k1::SecretKey::parse(&keccak_256(b"Alice")).unwrap() @@ -55,13 +56,13 @@ pub fn alice_secret() -> libsecp256k1::SecretKey { /// EIP712 Payload struct #[derive(Eip712, EthAbiType, Clone)] #[eip712( -name = "Astar EVM Claim", +name = "Astar EVM dispatch", version = "1", chain_id = 1024, // mock genisis hash raw_salt = "0x4545454545454545454545454545454545454545454545454545454545454545" )] -struct Claim { +struct Dispatch { substrate_address: ethers::core::types::Bytes, } @@ -69,7 +70,7 @@ struct Claim { pub fn get_evm_signature(who: &AccountId32, secret: &libsecp256k1::SecretKey) -> [u8; 65] { // sign the payload UnifiedAccounts::eth_sign_prehash( - &Claim { + &Dispatch { substrate_address: who.encode().into(), } .encode_eip712() @@ -111,11 +112,14 @@ impl frame_system::Config for TestRuntime { type MaxConsumers = frame_support::traits::ConstU32<16>; } -pub struct WhitelistedCalls(PhantomData); +pub struct WhitelistedCalls; -impl Contains for WhitelistedCalls { - fn contains(_t: &T) -> bool { - true +impl Contains for WhitelistedCalls { + fn contains(call: &RuntimeCall) -> bool { + match call { + RuntimeCall::Balances(_) => true, + _ => false, + } } } @@ -125,14 +129,13 @@ pub struct TestPrecompileSet(PhantomData); impl PrecompileSet for TestPrecompileSet where R: pallet_evm::Config, - DispatchLockdrop>>: - Precompile, + DispatchLockdrop>: Precompile, { fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { match handle.code_address() { a if a == PRECOMPILE_ADDRESS => Some(DispatchLockdrop::< R, - DispatchFilterValidate>, + DispatchFilterValidate, >::execute(handle)), _ => None, } @@ -185,10 +188,7 @@ parameter_types! { pub type PrecompileCall = DispatchLockdropCall< TestRuntime, - DispatchFilterValidate< - ::RuntimeCall, - WhitelistedCalls<::RuntimeCall>, - >, + DispatchFilterValidate<::RuntimeCall, WhitelistedCalls>, ConstU32<8>, >; diff --git a/precompiles/dispatch-lockdrop/src/tests.rs b/precompiles/dispatch-lockdrop/src/tests.rs index 258dd39840..d61a322467 100644 --- a/precompiles/dispatch-lockdrop/src/tests.rs +++ b/precompiles/dispatch-lockdrop/src/tests.rs @@ -31,7 +31,7 @@ fn precompiles() -> TestPrecompileSet { } #[test] -fn unify_lockdrop_account_works() { +fn dispatch_calls_on_behalf_of_lockdrop_works() { ExtBuilder::default().build().execute_with(|| { // Transfer balance to Alice let call = RuntimeCall::Balances(pallet_balances::Call::transfer { @@ -68,6 +68,76 @@ fn unify_lockdrop_account_works() { }); } +#[test] +fn account_id_from_payload_hash_should_match_derived_account_id_of_caller() { + ExtBuilder::default().build().execute_with(|| { + // Transfer balance to Alice + let call = RuntimeCall::Balances(pallet_balances::Call::transfer { + dest: ALICE, + value: 15 * ONE, + }); + // Sanity check - Alice holds no Balance + assert_eq!(Balances::free_balance(ALICE), 0); + + // Get Alice EVM address based on the Public Key + let alice_eth = UnifiedAccounts::eth_address(&alice_secret()); + // Dummy AccountId to sign the EIP712 payload with + let account_id = DUMMY; + // Fund this dummy account + let _ = Balances::deposit_creating(&account_id, ONE * 20); + // Sign the EIP712 payload with this dummy account + let sig = get_evm_signature(&account_id, &alice_secret()); + + precompiles() + .prepare_test( + alice_eth, + PRECOMPILE_ADDRESS, + PrecompileCall::dispatch_lockdrop_call { + call: call.encode().into(), + account_id: H256::from_slice(account_id.as_ref()), + signature: sig.into(), + }, + ) + .expect_no_logs() + .execute_returns(false); + + // Get Balance of ALICE in pallet balances and ensure it has not received any funds + assert_eq!(Balances::free_balance(ALICE), 0); + }); +} + +#[test] +fn only_whitelisted_calls_can_be_dispatched() { + ExtBuilder::default().build().execute_with(|| { + // Transfer balance to Alice + let call = RuntimeCall::System(frame_system::Call::remark_with_event { + remark: b"Hello World".to_vec(), + }); + + // Get Alice EVM address based on the Public Key + let alice_eth = UnifiedAccounts::eth_address(&alice_secret()); + // Get derived AccountId from the Blake2b hash of the compressed ECDSA Public key + let account_id = crate::tests::account_id(&alice_secret()); + // Fund this account (fund the lockdrop account) + let _ = Balances::deposit_creating(&account_id, ONE * 20); + // Sign the EIP712 payload + let sig = get_evm_signature(&account_id, &alice_secret()); + + precompiles() + .prepare_test( + alice_eth, + PRECOMPILE_ADDRESS, + PrecompileCall::dispatch_lockdrop_call { + call: call.encode().into(), + account_id: H256::from_slice(account_id.as_ref()), + signature: sig.into(), + }, + ) + .expect_no_logs() + .execute_returns(false); + }); +} + fn account_id(secret: &libsecp256k1::SecretKey) -> AccountId32 { sp_io::hashing::blake2_256( ecdsa::Public::from_full( diff --git a/runtime/shibuya/Cargo.toml b/runtime/shibuya/Cargo.toml index 4eafb194fa..ed86ae74d8 100644 --- a/runtime/shibuya/Cargo.toml +++ b/runtime/shibuya/Cargo.toml @@ -106,6 +106,7 @@ pallet-dynamic-evm-base-fee = { workspace = true } pallet-ethereum-checked = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } pallet-evm-precompile-dapp-staking-v3 = { workspace = true } +pallet-evm-precompile-dispatch-lockdrop = { workspace = true } pallet-evm-precompile-sr25519 = { workspace = true } pallet-evm-precompile-substrate-ecdsa = { workspace = true } pallet-evm-precompile-unified-accounts = { workspace = true } @@ -191,6 +192,7 @@ std = [ "pallet-evm-precompile-xcm/std", "pallet-evm-precompile-xvm/std", "pallet-evm-precompile-unified-accounts/std", + "pallet-evm-precompile-dispatch-lockdrop/std", "pallet-dapp-staking-v3/std", "dapp-staking-v3-runtime-api/std", "pallet-inflation/std", diff --git a/runtime/shibuya/src/precompiles.rs b/runtime/shibuya/src/precompiles.rs index c9f34a9a9d..8a70a1b8f2 100644 --- a/runtime/shibuya/src/precompiles.rs +++ b/runtime/shibuya/src/precompiles.rs @@ -20,12 +20,14 @@ use crate::{RuntimeCall, UnifiedAccounts, Xvm}; use astar_primitives::precompiles::DispatchFilterValidate; +use frame_support::pallet_prelude::ConstU32; use frame_support::{parameter_types, traits::Contains}; use pallet_evm_precompile_assets_erc20::Erc20AssetsPrecompileSet; use pallet_evm_precompile_blake2::Blake2F; use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use pallet_evm_precompile_dapp_staking_v3::DappStakingV3Precompile; use pallet_evm_precompile_dispatch::Dispatch; +use pallet_evm_precompile_dispatch_lockdrop::DispatchLockdrop; use pallet_evm_precompile_ed25519::Ed25519Verify; use pallet_evm_precompile_modexp::Modexp; use pallet_evm_precompile_sha3fips::Sha3FIPS256; @@ -125,6 +127,12 @@ pub type ShibuyaPrecompilesSetAt = ( UnifiedAccountsPrecompile, (CallableByContract, CallableByPrecompile), >, + PrecompileAt< + AddressU64<20487>, + DispatchLockdrop, ConstU32<8>>, + // Not callable from smart contract nor precompiled, only EOA accounts + (), + >, ); pub type ShibuyaPrecompiles = PrecompileSetBuilder< From eb0d085805d199cd85001bd6a4a9ea1082e4ca2b Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Fri, 26 Jan 2024 15:06:32 +0100 Subject: [PATCH 07/20] clean code --- precompiles/dispatch-lockdrop/src/lib.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/precompiles/dispatch-lockdrop/src/lib.rs b/precompiles/dispatch-lockdrop/src/lib.rs index ab28ee1bdf..0cbf7a680e 100644 --- a/precompiles/dispatch-lockdrop/src/lib.rs +++ b/precompiles/dispatch-lockdrop/src/lib.rs @@ -46,6 +46,8 @@ mod mock; #[cfg(test)] mod tests; +pub const LOG_TARGET: &str = "dispatch-lockdrop-precompile"; + // ECDSA signature bytes type ECDSASignatureBytes = ConstU32<65>; @@ -77,7 +79,7 @@ where signature: BoundedBytes, ) -> EvmResult { log::trace!( - target: "dispatch-lockdrop", + target: LOG_TARGET, "raw arguments: call: {:?}, account_id: {:?}, signature: {:?}", call, account_id, @@ -85,7 +87,6 @@ where ); let target_gas = handle.gas_limit(); - let caller: H160 = handle.context().caller.into(); let input: Vec = call.into(); let signature_bytes: Vec = signature.into(); @@ -97,7 +98,7 @@ where Ok(c) => c, Err(_) => { log::trace!( - target: "dispatch-lockdrop", + target: LOG_TARGET, "Error: could not decode call" ); return Ok(false); @@ -111,7 +112,7 @@ where <= Runtime::GasWeightMapping::gas_to_weight(gas, false).ref_time(); if !valid_weight { log::trace!( - target: "dispatch-lockdrop", + target: LOG_TARGET, "Error: gas limit exceeded" ); return Ok(false); @@ -123,7 +124,7 @@ where Some(s) => s, None => { log::trace!( - target: "dispatch-lockdrop", + target: LOG_TARGET, "Error: could not parse signature" ); return Ok(false); @@ -137,7 +138,7 @@ where Some(k) => k, None => { log::trace!( - target: "dispatch-lockdrop", + target: LOG_TARGET, "Error: could not recover pubkey from signature" ); return Ok(false); @@ -147,7 +148,7 @@ where // 4. Ensure that the caller matches the recovered EVM address from the signature if caller != Self::get_evm_address_from_pubkey(&pubkey) { log::trace!( - target: "dispatch-lockdrop", + target: LOG_TARGET, "Error: caller does not match calculated EVM address" ); return Ok(false); @@ -158,7 +159,7 @@ where Some(a) => a, None => { log::trace!( - target: "dispatch-lockdrop", + target: LOG_TARGET, "Error: could not derive AccountId from pubkey" ); return Ok(false); @@ -198,7 +199,7 @@ where } Err(e) => { log::trace!( - target: "rescue-lockdrop-precompile:claim_lock_drop_account", + target: LOG_TARGET, "Error: {:?}", e ); From f05afc89f21ad7233a598e0e9ebc8901ea13f3c4 Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Fri, 26 Jan 2024 15:06:57 +0100 Subject: [PATCH 08/20] fmt --- precompiles/dispatch-lockdrop/src/lib.rs | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/precompiles/dispatch-lockdrop/src/lib.rs b/precompiles/dispatch-lockdrop/src/lib.rs index 0cbf7a680e..dc5fa99e48 100644 --- a/precompiles/dispatch-lockdrop/src/lib.rs +++ b/precompiles/dispatch-lockdrop/src/lib.rs @@ -97,10 +97,7 @@ where match Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*input) { Ok(c) => c, Err(_) => { - log::trace!( - target: LOG_TARGET, - "Error: could not decode call" - ); + log::trace!(target: LOG_TARGET, "Error: could not decode call"); return Ok(false); } }; @@ -111,10 +108,7 @@ where let valid_weight = info.weight.ref_time() <= Runtime::GasWeightMapping::gas_to_weight(gas, false).ref_time(); if !valid_weight { - log::trace!( - target: LOG_TARGET, - "Error: gas limit exceeded" - ); + log::trace!(target: LOG_TARGET, "Error: gas limit exceeded"); return Ok(false); } } @@ -123,10 +117,7 @@ where let signature_opt = match Self::parse_signature(&signature_bytes) { Some(s) => s, None => { - log::trace!( - target: LOG_TARGET, - "Error: could not parse signature" - ); + log::trace!(target: LOG_TARGET, "Error: could not parse signature"); return Ok(false); } }; @@ -198,11 +189,7 @@ where Ok(true) } Err(e) => { - log::trace!( - target: LOG_TARGET, - "Error: {:?}", - e - ); + log::trace!(target: LOG_TARGET, "Error: {:?}", e); Ok(false) } } From b2989efa7c5ad77cc728a290f261b010b8ce126c Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Mon, 29 Jan 2024 17:17:39 +0100 Subject: [PATCH 09/20] PR review --- Cargo.lock | 1 - precompiles/dispatch-lockdrop/Cargo.toml | 21 ++-- precompiles/dispatch-lockdrop/src/lib.rs | 132 +++++++++++---------- precompiles/dispatch-lockdrop/src/mock.rs | 35 ++++-- precompiles/dispatch-lockdrop/src/tests.rs | 22 +++- runtime/local/src/precompiles.rs | 24 +++- runtime/shibuya/src/precompiles.rs | 24 +++- 7 files changed, 165 insertions(+), 94 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7bbbff568d..fb943fac44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8051,7 +8051,6 @@ dependencies = [ "pallet-evm", "pallet-evm-precompile-dispatch", "pallet-timestamp", - "pallet-unified-accounts", "parity-scale-codec", "precompile-utils", "scale-info", diff --git a/precompiles/dispatch-lockdrop/Cargo.toml b/precompiles/dispatch-lockdrop/Cargo.toml index 313da295c8..ac58f7ffea 100644 --- a/precompiles/dispatch-lockdrop/Cargo.toml +++ b/precompiles/dispatch-lockdrop/Cargo.toml @@ -15,7 +15,6 @@ libsecp256k1 = { workspace = true, features = ["hmac", "static-context"] } log = { workspace = true } pallet-evm = { workspace = true } pallet-evm-precompile-dispatch = { workspace = true } -pallet-unified-accounts = { workspace = true } parity-scale-codec = { workspace = true } precompile-utils = { workspace = true } sp-core = { workspace = true } @@ -39,17 +38,19 @@ sp-std = { workspace = true } [features] default = ["std"] std = [ - "pallet-unified-accounts/std", - "fp-evm/std", - "frame-support/std", - "frame-system/std", - "pallet-evm/std", - "precompile-utils/std", - "sp-core/std", + "log/std", + "libsecp256k1/std", + "parity-scale-codec/std", + "scale-info/std", "sp-std/std", + "sp-core/std", "sp-io/std", "sp-runtime/std", - "libsecp256k1/std", + "frame-support/std", + "frame-system/std", + "astar-primitives/std", + "precompile-utils/std", "pallet-evm/std", - "pallet-evm-precompile-dispatch/std", + "pallet-balances/std", + "pallet-timestamp/std", ] diff --git a/precompiles/dispatch-lockdrop/src/lib.rs b/precompiles/dispatch-lockdrop/src/lib.rs index dc5fa99e48..4331571e02 100644 --- a/precompiles/dispatch-lockdrop/src/lib.rs +++ b/precompiles/dispatch-lockdrop/src/lib.rs @@ -20,8 +20,9 @@ extern crate alloc; +use alloc::format; use core::marker::PhantomData; -use fp_evm::PrecompileHandle; +use fp_evm::{ExitError, PrecompileFailure, PrecompileHandle}; use frame_support::pallet_prelude::IsType; use frame_support::{codec::DecodeLimit as _, dispatch::Pays, traits::Get}; use frame_support::{ @@ -54,7 +55,7 @@ type ECDSASignatureBytes = ConstU32<65>; // `DecodeLimit` specifies the max depth a call can use when decoding, as unbounded depth // can be used to overflow the stack. // Default value is 8, which is the same as in XCM call decoding. -pub struct DispatchLockdrop>( +pub struct DispatchLockdrop>( PhantomData<(Runtime, DispatchValidator, DecodeLimit)>, ); @@ -62,7 +63,7 @@ pub struct DispatchLockdrop DispatchLockdrop where - Runtime: pallet_evm::Config + pallet_unified_accounts::Config, + Runtime: pallet_evm::Config, ::RuntimeOrigin: From>, Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, ::AccountId: IsType, @@ -86,7 +87,6 @@ where signature ); - let target_gas = handle.gas_limit(); let caller: H160 = handle.context().caller.into(); let input: Vec = call.into(); let signature_bytes: Vec = signature.into(); @@ -97,74 +97,62 @@ where match Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*input) { Ok(c) => c, Err(_) => { - log::trace!(target: LOG_TARGET, "Error: could not decode call"); - return Ok(false); + let message: &str = "Error: could not decode call"; + log::trace!(target: LOG_TARGET, "{}", message); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other(message.into()), + }); } }; - // 2. Check that gas limit is not exceeded let info = call.get_dispatch_info(); - if let Some(gas) = target_gas { - let valid_weight = info.weight.ref_time() - <= Runtime::GasWeightMapping::gas_to_weight(gas, false).ref_time(); - if !valid_weight { - log::trace!(target: LOG_TARGET, "Error: gas limit exceeded"); - return Ok(false); - } - } + handle + .record_external_cost(Some(info.weight.ref_time()), Some(info.weight.proof_size()))?; + + // 2. Recover the ECDSA Public key from the signature + let signature_opt = unwrap_or_err!( + Self::parse_signature(&signature_bytes), + "Error: could not parse signature" + ); - // 3. Recover the ECDSA Public key from the signature - let signature_opt = match Self::parse_signature(&signature_bytes) { - Some(s) => s, - None => { - log::trace!(target: LOG_TARGET, "Error: could not parse signature"); - return Ok(false); - } - }; let payload_hash = Self::build_signing_payload(&account_id); - let pubkey = match >::recover_pubkey( - signature_opt.as_ref(), - payload_hash, - ) { - Some(k) => k, - None => { - log::trace!( - target: LOG_TARGET, - "Error: could not recover pubkey from signature" - ); - return Ok(false); - } - }; + let pubkey = unwrap_or_err!( + sp_io::crypto::secp256k1_ecdsa_recover(signature_opt.as_ref(), &payload_hash).ok(), + "Error: could not recover pubkey from signature" + ); - // 4. Ensure that the caller matches the recovered EVM address from the signature + // 3. Ensure that the caller matches the recovered EVM address from the signature if caller != Self::get_evm_address_from_pubkey(&pubkey) { - log::trace!( - target: LOG_TARGET, - "Error: caller does not match calculated EVM address" - ); - return Ok(false); + let message: &str = "Error: caller does not match calculated EVM address"; + log::trace!(target: LOG_TARGET, "{}", message); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other(message.into()), + }); } - // 5. Derive the AccountId from the ECDSA compressed Public key - let origin = match Self::get_account_id_from_pubkey(pubkey) { - Some(a) => a, - None => { - log::trace!( - target: LOG_TARGET, - "Error: could not derive AccountId from pubkey" - ); - return Ok(false); - } - }; + // 4. Derive the AccountId from the ECDSA compressed Public key + let origin = unwrap_or_err!( + Self::get_account_id_from_pubkey(pubkey), + "Error: could not derive AccountId from pubkey" + ); - // 6. validate the call - if let Some(_) = DispatchValidator::validate_before_dispatch(&origin, &call) { - return Ok(false); + if origin != account_id { + let message: &str = + "Error: AccountId parsed from signature does not match the one provided"; + log::trace!(target: LOG_TARGET, "{}", message); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other(message.into()), + }); } - handle - .record_external_cost(Some(info.weight.ref_time()), Some(info.weight.proof_size()))?; + // 5. validate the call + if let Some(err) = DispatchValidator::validate_before_dispatch(&origin, &call) { + let message: &str = "Error: could not validate call"; + log::trace!(target: LOG_TARGET, "{}", message); + return Err(err); + } + // 6. Dispatch the callĂ’ match call.dispatch(Some(origin).into()) { Ok(post_info) => { if post_info.pays_fee(&info) == Pays::Yes { @@ -189,8 +177,12 @@ where Ok(true) } Err(e) => { - log::trace!(target: LOG_TARGET, "Error: {:?}", e); - Ok(false) + log::trace!(target: LOG_TARGET, "{:?}", e); + Err(PrecompileFailure::Error { + exit_status: ExitError::Other( + format!("dispatch execution failed: {}", <&'static str>::from(e)).into(), + ), + }) } } } @@ -226,9 +218,7 @@ where domain.extend_from_slice(&keccak256!("Astar EVM dispatch")); // name domain.extend_from_slice(&keccak256!("1")); // version domain.extend_from_slice( - &(<[u8; 32]>::from(U256::from( - ::ChainId::get(), - ))), + &(<[u8; 32]>::from(U256::from(::ChainId::get()))), ); // chain id domain.extend_from_slice( frame_system::Pallet::::block_hash(::BlockNumber::zero()) @@ -243,3 +233,19 @@ where keccak_256(args_hash.as_slice()) } } + +#[macro_export] +macro_rules! unwrap_or_err { + ($option_expr:expr, $error_msg:expr) => { + match $option_expr { + Some(value) => value, + None => { + let message: &str = $error_msg; + log::trace!(target: LOG_TARGET, "{}", message); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other(message.into()), + }); + } + } + }; +} diff --git a/precompiles/dispatch-lockdrop/src/mock.rs b/precompiles/dispatch-lockdrop/src/mock.rs index 7d7cd9ce7e..19473b5547 100644 --- a/precompiles/dispatch-lockdrop/src/mock.rs +++ b/precompiles/dispatch-lockdrop/src/mock.rs @@ -37,7 +37,6 @@ use ethers::prelude::transaction::eip712::Eip712; use frame_support::traits::Contains; -use astar_primitives::evm::HashedDefaultMappings; use astar_primitives::precompiles::DispatchFilterValidate; pub type AccountId = AccountId32; pub type Balance = u128; @@ -69,7 +68,7 @@ struct Dispatch { /// Build the signature payload for given native account and eth private key pub fn get_evm_signature(who: &AccountId32, secret: &libsecp256k1::SecretKey) -> [u8; 65] { // sign the payload - UnifiedAccounts::eth_sign_prehash( + eth_sign_prehash( &Dispatch { substrate_address: who.encode().into(), } @@ -79,6 +78,14 @@ pub fn get_evm_signature(who: &AccountId32, secret: &libsecp256k1::SecretKey) -> ) } +pub fn eth_sign_prehash(prehash: &[u8; 32], secret: &libsecp256k1::SecretKey) -> [u8; 65] { + let (sig, recovery_id) = libsecp256k1::sign(&libsecp256k1::Message::parse(prehash), secret); + let mut r = [0u8; 65]; + r[0..64].copy_from_slice(&sig.serialize()[..]); + r[64] = recovery_id.serialize(); + r +} + parameter_types! { pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = @@ -192,13 +199,25 @@ pub type PrecompileCall = DispatchLockdropCall< ConstU32<8>, >; +pub struct AddressMapper; +impl AddressMapping for AddressMapper { + fn into_account_id(account: H160) -> astar_primitives::AccountId { + let mut account_id = [0u8; 32]; + account_id[0..20].clone_from_slice(&account.as_bytes()); + + account_id + .try_into() + .expect("H160 is 20 bytes long so it must fit into 32 bytes; QED") + } +} + impl pallet_evm::Config for TestRuntime { type FeeCalculator = (); type GasWeightMapping = pallet_evm::FixedGasWeightMapping; type WeightPerGas = WeightPerGas; type CallOrigin = EnsureAddressRoot; type WithdrawOrigin = EnsureAddressNever; - type AddressMapping = UnifiedAccounts; + type AddressMapping = AddressMapper; type Currency = Balances; type RuntimeEvent = RuntimeEvent; type Runner = pallet_evm::runner::stack::Runner; @@ -221,15 +240,6 @@ parameter_types! { pub ChainId: u64 = 1024; } -impl pallet_unified_accounts::Config for TestRuntime { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type DefaultMappings = HashedDefaultMappings; - type ChainId = ChainId; - type AccountMappingStorageFee = AccountMappingStorageFee; - type WeightInfo = pallet_unified_accounts::weights::SubstrateWeight; -} - // Configure a mock runtime to test the pallet. construct_runtime!( pub enum TestRuntime where @@ -239,7 +249,6 @@ construct_runtime!( { System: frame_system, Evm: pallet_evm, - UnifiedAccounts: pallet_unified_accounts, Balances : pallet_balances, Timestamp: pallet_timestamp, } diff --git a/precompiles/dispatch-lockdrop/src/tests.rs b/precompiles/dispatch-lockdrop/src/tests.rs index d61a322467..e9ed204ef0 100644 --- a/precompiles/dispatch-lockdrop/src/tests.rs +++ b/precompiles/dispatch-lockdrop/src/tests.rs @@ -17,11 +17,13 @@ // along with Astar. If not, see . use ethers::prelude::H256; +use fp_evm::ExitError; use frame_support::traits::Currency; use sp_core::crypto::AccountId32; use crate::mock::*; +use astar_primitives::evm::EvmAddress; use parity_scale_codec::Encode; use precompile_utils::testing::*; use sp_core::ecdsa; @@ -42,7 +44,7 @@ fn dispatch_calls_on_behalf_of_lockdrop_works() { assert_eq!(Balances::free_balance(ALICE), 0); // Get Alice EVM address based on the Public Key - let alice_eth = UnifiedAccounts::eth_address(&alice_secret()); + let alice_eth = crate::tests::eth_address(&alice_secret()); // Get derived AccountId from the Blake2b hash of the compressed ECDSA Public key let account_id = account_id(&alice_secret()); // Fund this account (fund the lockdrop account) @@ -80,7 +82,7 @@ fn account_id_from_payload_hash_should_match_derived_account_id_of_caller() { assert_eq!(Balances::free_balance(ALICE), 0); // Get Alice EVM address based on the Public Key - let alice_eth = UnifiedAccounts::eth_address(&alice_secret()); + let alice_eth = crate::tests::eth_address(&alice_secret()); // Dummy AccountId to sign the EIP712 payload with let account_id = DUMMY; // Fund this dummy account @@ -99,7 +101,9 @@ fn account_id_from_payload_hash_should_match_derived_account_id_of_caller() { }, ) .expect_no_logs() - .execute_returns(false); + .execute_error(ExitError::Other( + "Error: AccountId parsed from signature does not match the one provided".into(), + )); // Get Balance of ALICE in pallet balances and ensure it has not received any funds assert_eq!(Balances::free_balance(ALICE), 0); @@ -115,7 +119,7 @@ fn only_whitelisted_calls_can_be_dispatched() { }); // Get Alice EVM address based on the Public Key - let alice_eth = UnifiedAccounts::eth_address(&alice_secret()); + let alice_eth = crate::tests::eth_address(&alice_secret()); // Get derived AccountId from the Blake2b hash of the compressed ECDSA Public key let account_id = crate::tests::account_id(&alice_secret()); // Fund this account (fund the lockdrop account) @@ -134,7 +138,7 @@ fn only_whitelisted_calls_can_be_dispatched() { }, ) .expect_no_logs() - .execute_returns(false); + .execute_error(ExitError::Other("call filtered out".into())) }); } @@ -148,3 +152,11 @@ fn account_id(secret: &libsecp256k1::SecretKey) -> AccountId32 { ) .into() } + +fn eth_address(secret: &libsecp256k1::SecretKey) -> EvmAddress { + EvmAddress::from_slice( + &sp_io::hashing::keccak_256( + &libsecp256k1::PublicKey::from_secret_key(secret).serialize()[1..65], + )[12..], + ) +} diff --git a/runtime/local/src/precompiles.rs b/runtime/local/src/precompiles.rs index 136852f80e..fb779533f1 100644 --- a/runtime/local/src/precompiles.rs +++ b/runtime/local/src/precompiles.rs @@ -66,6 +66,24 @@ impl Contains for WhitelistedCalls { } } } + +/// Filter that only allows whitelisted runtime call to pass through dispatch-lockdrop precompile +pub struct WhitelistedLockdropCalls; + +impl Contains for WhitelistedLockdropCalls { + fn contains(t: &RuntimeCall) -> bool { + match t { + RuntimeCall::Utility(pallet_utility::Call::batch { calls }) + | RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) => calls + .iter() + .all(|call| WhitelistedLockdropCalls::contains(call)), + RuntimeCall::DappsStaking(_) => true, + RuntimeCall::Assets(pallet_assets::Call::transfer { .. }) => true, + _ => false, + } + } +} + /// The PrecompileSet installed in the Local runtime. #[precompile_utils::precompile_name_from_address] pub type LocalPrecompilesSetAt = ( @@ -120,7 +138,11 @@ pub type LocalPrecompilesSetAt = ( >, PrecompileAt< AddressU64<20487>, - DispatchLockdrop, ConstU32<8>>, + DispatchLockdrop< + R, + DispatchFilterValidate, + ConstU32<8>, + >, // Not callable from smart contract nor precompiled, only EOA accounts (), >, diff --git a/runtime/shibuya/src/precompiles.rs b/runtime/shibuya/src/precompiles.rs index 8a70a1b8f2..1f6dacaebb 100644 --- a/runtime/shibuya/src/precompiles.rs +++ b/runtime/shibuya/src/precompiles.rs @@ -67,6 +67,24 @@ impl Contains for WhitelistedCalls { } } } + +/// Filter that only allows whitelisted runtime call to pass through dispatch-lockdrop precompile +pub struct WhitelistedLockdropCalls; + +impl Contains for WhitelistedLockdropCalls { + fn contains(t: &RuntimeCall) -> bool { + match t { + RuntimeCall::Utility(pallet_utility::Call::batch { calls }) + | RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) => calls + .iter() + .all(|call| WhitelistedLockdropCalls::contains(call)), + RuntimeCall::DappStaking(_) => true, + RuntimeCall::Assets(pallet_assets::Call::transfer { .. }) => true, + _ => false, + } + } +} + /// The PrecompileSet installed in the Shibuya runtime. #[precompile_utils::precompile_name_from_address] pub type ShibuyaPrecompilesSetAt = ( @@ -129,7 +147,11 @@ pub type ShibuyaPrecompilesSetAt = ( >, PrecompileAt< AddressU64<20487>, - DispatchLockdrop, ConstU32<8>>, + DispatchLockdrop< + R, + DispatchFilterValidate, + ConstU32<8>, + >, // Not callable from smart contract nor precompiled, only EOA accounts (), >, From a73eb88d15b7f093c42c9eb50063ac13e7bde562 Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Mon, 29 Jan 2024 17:24:54 +0100 Subject: [PATCH 10/20] clean code --- precompiles/dispatch-lockdrop/src/lib.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/precompiles/dispatch-lockdrop/src/lib.rs b/precompiles/dispatch-lockdrop/src/lib.rs index 4331571e02..c78e43db5a 100644 --- a/precompiles/dispatch-lockdrop/src/lib.rs +++ b/precompiles/dispatch-lockdrop/src/lib.rs @@ -93,17 +93,10 @@ where let account_id = AccountId32::new(account_id.into()).into(); // 1. Decode the call - let call = - match Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*input) { - Ok(c) => c, - Err(_) => { - let message: &str = "Error: could not decode call"; - log::trace!(target: LOG_TARGET, "{}", message); - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other(message.into()), - }); - } - }; + let call = unwrap_or_err!( + Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*input).ok(), + "Error: could not decode call" + ); let info = call.get_dispatch_info(); handle From 299cfe35adefd678e9ce090881ed7b7c2e1d91b7 Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Tue, 30 Jan 2024 13:41:15 +0100 Subject: [PATCH 11/20] revert UA pallet changes --- pallets/unified-accounts/src/lib.rs | 15 ++++----- pallets/unified-accounts/src/tests.rs | 46 ++++----------------------- 2 files changed, 14 insertions(+), 47 deletions(-) diff --git a/pallets/unified-accounts/src/lib.rs b/pallets/unified-accounts/src/lib.rs index 521ebeb75b..358f4c12b3 100644 --- a/pallets/unified-accounts/src/lib.rs +++ b/pallets/unified-accounts/src/lib.rs @@ -76,7 +76,6 @@ use frame_support::{ }, }; use frame_system::{ensure_signed, pallet_prelude::*}; -pub use pallet::*; use pallet_evm::AddressMapping; use precompile_utils::keccak256; use sp_core::{H160, H256, U256}; @@ -87,6 +86,8 @@ use sp_runtime::{ }; use sp_std::marker::PhantomData; +pub use pallet::*; + pub mod weights; pub use weights::WeightInfo; @@ -192,8 +193,7 @@ pub mod pallet { ); // recover evm address from signature - let payload_hash = Self::build_signing_payload(&who); - let address = Self::verify_signature(&signature, payload_hash) + let address = Self::verify_signature(&who, &signature) .ok_or(Error::::UnexpectedSignatureFormat)?; ensure!(evm_address == address, Error::::InvalidSignature); @@ -296,13 +296,12 @@ impl Pallet { keccak_256(&payload) } - pub fn recover_pubkey(sig: &EvmSignature, payload_hash: [u8; 32]) -> Option<[u8; 64]> { - sp_io::crypto::secp256k1_ecdsa_recover(sig, &payload_hash).ok() - } + pub fn verify_signature(who: &T::AccountId, sig: &EvmSignature) -> Option { + let payload_hash = Self::build_signing_payload(who); - pub fn verify_signature(sig: &EvmSignature, payload_hash: [u8; 32]) -> Option { - Self::recover_pubkey(sig, payload_hash) + sp_io::crypto::secp256k1_ecdsa_recover(sig, &payload_hash) .map(|pubkey| H160::from(H256::from_slice(&keccak_256(&pubkey)))) + .ok() } fn build_domain_separator() -> [u8; 32] { diff --git a/pallets/unified-accounts/src/tests.rs b/pallets/unified-accounts/src/tests.rs index b5225a2814..68dcc88ae7 100644 --- a/pallets/unified-accounts/src/tests.rs +++ b/pallets/unified-accounts/src/tests.rs @@ -32,12 +32,12 @@ use sp_runtime::{traits::StaticLookup, AccountId32, MultiAddress}; /// EIP712 Payload struct #[derive(Eip712, EthAbiType, Clone)] #[eip712( - name = "Astar EVM Claim", - version = "1", - chain_id = 1024, - // mock genisis hash - raw_salt = "0x4545454545454545454545454545454545454545454545454545454545454545" - )] +name = "Astar EVM Claim", +version = "1", +chain_id = 1024, +// mock genisis hash +raw_salt = "0x4545454545454545454545454545454545454545454545454545454545454545" +)] struct Claim { substrate_address: Bytes, } @@ -83,7 +83,7 @@ fn eip712_signature_verify_works() { let sig = UnifiedAccounts::eth_sign_prehash(&claim_hash, &alice_secret()); assert_eq!( Some(UnifiedAccounts::eth_address(&alice_secret())), - UnifiedAccounts::verify_signature(&sig, claim_hash), + UnifiedAccounts::verify_signature(&ALICE, &sig), "signature verification should work" ); }); @@ -397,35 +397,3 @@ fn charging_storage_fee_should_not_reap_account() { ); }); } - -#[test] -fn recover_public_key_works() { - ExtBuilder::default().build().execute_with(|| { - let claim = Claim { - substrate_address: ALICE.encode().into(), - }; - - let claim_hash = UnifiedAccounts::build_signing_payload(&ALICE); - // assert signing payload is correct - assert_eq!( - claim.encode_eip712().unwrap(), - claim_hash, - "signing payload should match" - ); - - // sign the payload - let signature = UnifiedAccounts::eth_sign_prehash(&claim_hash, &alice_secret()); - - let expected_pubkey = - &libsecp256k1::PublicKey::from_secret_key(&alice_secret()).serialize()[1..65]; - - // assert public key recovery works - assert_eq!( - expected_pubkey, - UnifiedAccounts::recover_pubkey(&signature, claim_hash) - .unwrap() - .as_ref(), - "recover public key should work" - ); - }); -} From 85dd6d03e447c5d93b74dada3c9985eda4760743 Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Tue, 30 Jan 2024 13:43:19 +0100 Subject: [PATCH 12/20] fmr --- pallets/unified-accounts/src/tests.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pallets/unified-accounts/src/tests.rs b/pallets/unified-accounts/src/tests.rs index 68dcc88ae7..0896ef2529 100644 --- a/pallets/unified-accounts/src/tests.rs +++ b/pallets/unified-accounts/src/tests.rs @@ -32,12 +32,12 @@ use sp_runtime::{traits::StaticLookup, AccountId32, MultiAddress}; /// EIP712 Payload struct #[derive(Eip712, EthAbiType, Clone)] #[eip712( -name = "Astar EVM Claim", -version = "1", -chain_id = 1024, -// mock genisis hash -raw_salt = "0x4545454545454545454545454545454545454545454545454545454545454545" -)] + name = "Astar EVM Claim", + version = "1", + chain_id = 1024, + // mock genisis hash + raw_salt = "0x4545454545454545454545454545454545454545454545454545454545454545" + )] struct Claim { substrate_address: Bytes, } From d6e38dc5c5975bbd0a8ad271af085f1d26c4a3dc Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Tue, 30 Jan 2024 14:50:24 +0100 Subject: [PATCH 13/20] PR review --- precompiles/dispatch-lockdrop/src/lib.rs | 127 ++++----------------- precompiles/dispatch-lockdrop/src/mock.rs | 37 ------ precompiles/dispatch-lockdrop/src/tests.rs | 32 +++--- 3 files changed, 38 insertions(+), 158 deletions(-) diff --git a/precompiles/dispatch-lockdrop/src/lib.rs b/precompiles/dispatch-lockdrop/src/lib.rs index c78e43db5a..ff08bd922e 100644 --- a/precompiles/dispatch-lockdrop/src/lib.rs +++ b/precompiles/dispatch-lockdrop/src/lib.rs @@ -32,14 +32,10 @@ use frame_support::{ use frame_system::Config; use pallet_evm::GasWeightMapping; use pallet_evm_precompile_dispatch::DispatchValidateT; -use parity_scale_codec::Encode; -use precompile_utils::prelude::{BoundedBytes, UnboundedBytes}; -use precompile_utils::{keccak256, EvmResult}; -use sp_core::ecdsa::Signature; +use precompile_utils::prelude::{revert, BoundedBytes, UnboundedBytes}; +use precompile_utils::EvmResult; use sp_core::{crypto::AccountId32, H160, H256}; -use sp_core::{ecdsa, U256}; use sp_io::hashing::keccak_256; -use sp_runtime::traits::Zero; use sp_std::vec::Vec; #[cfg(test)] @@ -47,10 +43,10 @@ mod mock; #[cfg(test)] mod tests; -pub const LOG_TARGET: &str = "dispatch-lockdrop-precompile"; +pub const LOG_TARGET: &str = "precompile::dispatch-lockdrop"; -// ECDSA signature bytes -type ECDSASignatureBytes = ConstU32<65>; +// ECDSA PublicKey +type ECDSAPublic = ConstU32<64>; // `DecodeLimit` specifies the max depth a call can use when decoding, as unbounded depth // can be used to overflow the stack. @@ -72,80 +68,51 @@ where DispatchValidateT<::AccountId, ::RuntimeCall>, DecodeLimit: Get, { - #[precompile::public("dispatch_lockdrop_call(bytes,bytes32,bytes)")] + #[precompile::public("dispatch_lockdrop_call(bytes,bytes)")] fn dispatch_lockdrop_call( handle: &mut impl PrecompileHandle, call: UnboundedBytes, - account_id: H256, - signature: BoundedBytes, + pubkey: BoundedBytes, ) -> EvmResult { log::trace!( target: LOG_TARGET, - "raw arguments: call: {:?}, account_id: {:?}, signature: {:?}", + "raw arguments: call: {:?}, pubkey: {:?}", call, - account_id, - signature + pubkey ); let caller: H160 = handle.context().caller.into(); let input: Vec = call.into(); - let signature_bytes: Vec = signature.into(); - let account_id = AccountId32::new(account_id.into()).into(); // 1. Decode the call - let call = unwrap_or_err!( - Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*input).ok(), - "Error: could not decode call" - ); + let call = Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*input) + .map_err(|_| revert("could not decode call"))?; let info = call.get_dispatch_info(); handle .record_external_cost(Some(info.weight.ref_time()), Some(info.weight.proof_size()))?; - // 2. Recover the ECDSA Public key from the signature - let signature_opt = unwrap_or_err!( - Self::parse_signature(&signature_bytes), - "Error: could not parse signature" - ); - - let payload_hash = Self::build_signing_payload(&account_id); - let pubkey = unwrap_or_err!( - sp_io::crypto::secp256k1_ecdsa_recover(signature_opt.as_ref(), &payload_hash).ok(), - "Error: could not recover pubkey from signature" - ); - - // 3. Ensure that the caller matches the recovered EVM address from the signature - if caller != Self::get_evm_address_from_pubkey(&pubkey) { - let message: &str = "Error: caller does not match calculated EVM address"; + // 2. Ensure that the caller matches the public key + if caller != Self::get_evm_address_from_pubkey(pubkey.as_bytes()) { + let message: &str = "caller does not match the public key"; log::trace!(target: LOG_TARGET, "{}", message); return Err(PrecompileFailure::Error { exit_status: ExitError::Other(message.into()), }); } - // 4. Derive the AccountId from the ECDSA compressed Public key - let origin = unwrap_or_err!( - Self::get_account_id_from_pubkey(pubkey), - "Error: could not derive AccountId from pubkey" - ); + // 3. Derive the AccountId from the ECDSA compressed Public key + let origin = Self::get_account_id_from_pubkey(pubkey.as_bytes()) + .ok_or_else(|| revert("could not derive AccountId from pubkey"))?; - if origin != account_id { - let message: &str = - "Error: AccountId parsed from signature does not match the one provided"; - log::trace!(target: LOG_TARGET, "{}", message); - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other(message.into()), - }); - } - - // 5. validate the call + // 4. validate the call if let Some(err) = DispatchValidator::validate_before_dispatch(&origin, &call) { let message: &str = "Error: could not validate call"; log::trace!(target: LOG_TARGET, "{}", message); return Err(err); } - // 6. Dispatch the callĂ’ + // 5. Dispatch the call match call.dispatch(Some(origin).into()) { Ok(post_info) => { if post_info.pays_fee(&info) == Pays::Yes { @@ -180,65 +147,13 @@ where } } - fn get_account_id_from_pubkey(pubkey: [u8; 64]) -> Option<::AccountId> { - libsecp256k1::PublicKey::parse_slice(&pubkey, None) + fn get_account_id_from_pubkey(pubkey: &[u8]) -> Option<::AccountId> { + libsecp256k1::PublicKey::parse_slice(pubkey, None) .map(|k| sp_io::hashing::blake2_256(k.serialize_compressed().as_ref()).into()) .ok() } - fn parse_signature(signature_bytes: &Vec) -> Option { - ecdsa::Signature::from_slice(&signature_bytes[..]) - } - fn get_evm_address_from_pubkey(pubkey: &[u8]) -> H160 { H160::from(H256::from_slice(&keccak_256(pubkey))) } - - fn build_signing_payload(who: &::AccountId) -> [u8; 32] { - let domain_separator = Self::build_domain_separator(); - let args_hash = Self::build_args_hash(who); - - let mut payload = b"\x19\x01".to_vec(); - payload.extend_from_slice(&domain_separator); - payload.extend_from_slice(&args_hash); - keccak_256(&payload) - } - - fn build_domain_separator() -> [u8; 32] { - let mut domain = - keccak256!("EIP712Domain(string name,string version,uint256 chainId,bytes32 salt)") - .to_vec(); - domain.extend_from_slice(&keccak256!("Astar EVM dispatch")); // name - domain.extend_from_slice(&keccak256!("1")); // version - domain.extend_from_slice( - &(<[u8; 32]>::from(U256::from(::ChainId::get()))), - ); // chain id - domain.extend_from_slice( - frame_system::Pallet::::block_hash(::BlockNumber::zero()) - .as_ref(), - ); // genesis block hash - keccak_256(domain.as_slice()) - } - - fn build_args_hash(account: &::AccountId) -> [u8; 32] { - let mut args_hash = keccak256!("Dispatch(bytes substrateAddress)").to_vec(); - args_hash.extend_from_slice(&keccak_256(&account.encode())); - keccak_256(args_hash.as_slice()) - } -} - -#[macro_export] -macro_rules! unwrap_or_err { - ($option_expr:expr, $error_msg:expr) => { - match $option_expr { - Some(value) => value, - None => { - let message: &str = $error_msg; - log::trace!(target: LOG_TARGET, "{}", message); - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other(message.into()), - }); - } - } - }; } diff --git a/precompiles/dispatch-lockdrop/src/mock.rs b/precompiles/dispatch-lockdrop/src/mock.rs index 19473b5547..3f3a2aba7b 100644 --- a/precompiles/dispatch-lockdrop/src/mock.rs +++ b/precompiles/dispatch-lockdrop/src/mock.rs @@ -24,7 +24,6 @@ use frame_support::{construct_runtime, parameter_types, traits::ConstU64, weight pub use pallet_evm::{ AddressMapping, EnsureAddressNever, EnsureAddressRoot, PrecompileResult, PrecompileSet, }; -use parity_scale_codec::Encode; use sp_core::{keccak_256, H160, H256}; use sp_runtime::{ testing::Header, @@ -32,9 +31,6 @@ use sp_runtime::{ AccountId32, }; -use ethers::contract::{Eip712, EthAbiType}; -use ethers::prelude::transaction::eip712::Eip712; - use frame_support::traits::Contains; use astar_primitives::precompiles::DispatchFilterValidate; @@ -52,39 +48,6 @@ pub const DUMMY: AccountId32 = AccountId32::new([2u8; 32]); pub fn alice_secret() -> libsecp256k1::SecretKey { libsecp256k1::SecretKey::parse(&keccak_256(b"Alice")).unwrap() } -/// EIP712 Payload struct -#[derive(Eip712, EthAbiType, Clone)] -#[eip712( -name = "Astar EVM dispatch", -version = "1", -chain_id = 1024, -// mock genisis hash -raw_salt = "0x4545454545454545454545454545454545454545454545454545454545454545" -)] -struct Dispatch { - substrate_address: ethers::core::types::Bytes, -} - -/// Build the signature payload for given native account and eth private key -pub fn get_evm_signature(who: &AccountId32, secret: &libsecp256k1::SecretKey) -> [u8; 65] { - // sign the payload - eth_sign_prehash( - &Dispatch { - substrate_address: who.encode().into(), - } - .encode_eip712() - .unwrap(), - secret, - ) -} - -pub fn eth_sign_prehash(prehash: &[u8; 32], secret: &libsecp256k1::SecretKey) -> [u8; 65] { - let (sig, recovery_id) = libsecp256k1::sign(&libsecp256k1::Message::parse(prehash), secret); - let mut r = [0u8; 65]; - r[0..64].copy_from_slice(&sig.serialize()[..]); - r[64] = recovery_id.serialize(); - r -} parameter_types! { pub const BlockHashCount: u64 = 250; diff --git a/precompiles/dispatch-lockdrop/src/tests.rs b/precompiles/dispatch-lockdrop/src/tests.rs index e9ed204ef0..a1141cdb46 100644 --- a/precompiles/dispatch-lockdrop/src/tests.rs +++ b/precompiles/dispatch-lockdrop/src/tests.rs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . -use ethers::prelude::H256; use fp_evm::ExitError; use frame_support::traits::Currency; use sp_core::crypto::AccountId32; @@ -49,8 +48,8 @@ fn dispatch_calls_on_behalf_of_lockdrop_works() { let account_id = account_id(&alice_secret()); // Fund this account (fund the lockdrop account) let _ = Balances::deposit_creating(&account_id, ONE * 20); - // Sign the EIP712 payload - let sig = get_evm_signature(&account_id, &alice_secret()); + // Get the full 64 bytes ECDSA Public key + let pubkey = crate::tests::public_key_full(&alice_secret()); precompiles() .prepare_test( @@ -58,8 +57,7 @@ fn dispatch_calls_on_behalf_of_lockdrop_works() { PRECOMPILE_ADDRESS, PrecompileCall::dispatch_lockdrop_call { call: call.encode().into(), - account_id: H256::from_slice(account_id.as_ref()), - signature: sig.into(), + pubkey: pubkey.into(), }, ) .expect_no_logs() @@ -71,7 +69,7 @@ fn dispatch_calls_on_behalf_of_lockdrop_works() { } #[test] -fn account_id_from_payload_hash_should_match_derived_account_id_of_caller() { +fn pubkey_does_not_match_caller_address() { ExtBuilder::default().build().execute_with(|| { // Transfer balance to Alice let call = RuntimeCall::Balances(pallet_balances::Call::transfer { @@ -87,8 +85,8 @@ fn account_id_from_payload_hash_should_match_derived_account_id_of_caller() { let account_id = DUMMY; // Fund this dummy account let _ = Balances::deposit_creating(&account_id, ONE * 20); - // Sign the EIP712 payload with this dummy account - let sig = get_evm_signature(&account_id, &alice_secret()); + // Create a dummy pubkey + let pubkey = [10u8; 64]; precompiles() .prepare_test( @@ -96,13 +94,12 @@ fn account_id_from_payload_hash_should_match_derived_account_id_of_caller() { PRECOMPILE_ADDRESS, PrecompileCall::dispatch_lockdrop_call { call: call.encode().into(), - account_id: H256::from_slice(account_id.as_ref()), - signature: sig.into(), + pubkey: pubkey.into(), }, ) .expect_no_logs() .execute_error(ExitError::Other( - "Error: AccountId parsed from signature does not match the one provided".into(), + "caller does not match the public key".into(), )); // Get Balance of ALICE in pallet balances and ensure it has not received any funds @@ -124,8 +121,8 @@ fn only_whitelisted_calls_can_be_dispatched() { let account_id = crate::tests::account_id(&alice_secret()); // Fund this account (fund the lockdrop account) let _ = Balances::deposit_creating(&account_id, ONE * 20); - // Sign the EIP712 payload - let sig = get_evm_signature(&account_id, &alice_secret()); + // Get the full 64 bytes ECDSA Public key + let pubkey = crate::tests::public_key_full(&alice_secret()); precompiles() .prepare_test( @@ -133,8 +130,7 @@ fn only_whitelisted_calls_can_be_dispatched() { PRECOMPILE_ADDRESS, PrecompileCall::dispatch_lockdrop_call { call: call.encode().into(), - account_id: H256::from_slice(account_id.as_ref()), - signature: sig.into(), + pubkey: pubkey.into(), }, ) .expect_no_logs() @@ -160,3 +156,9 @@ fn eth_address(secret: &libsecp256k1::SecretKey) -> EvmAddress { )[12..], ) } + +fn public_key_full(secret: &libsecp256k1::SecretKey) -> [u8; 64] { + libsecp256k1::PublicKey::from_secret_key(secret).serialize()[1..65] + .try_into() + .unwrap() +} From ed2cea0a8f407ffd6ed5df972c8d1565c2f1ce12 Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Wed, 31 Jan 2024 14:57:15 +0100 Subject: [PATCH 14/20] PR review --- Cargo.lock | 1 + pallets/unified-accounts/src/weights.rs | 2 +- precompiles/dispatch-lockdrop/Cargo.toml | 1 + .../dispatch-lockdrop/DispatchLockdrop.sol | 20 +++++ precompiles/dispatch-lockdrop/src/lib.rs | 51 +++++++----- precompiles/dispatch-lockdrop/src/mock.rs | 12 ++- precompiles/dispatch-lockdrop/src/tests.rs | 81 +++++++++++++++++-- runtime/local/src/precompiles.rs | 10 ++- runtime/shibuya/src/precompiles.rs | 10 ++- 9 files changed, 157 insertions(+), 31 deletions(-) create mode 100644 precompiles/dispatch-lockdrop/DispatchLockdrop.sol diff --git a/Cargo.lock b/Cargo.lock index fb943fac44..b3158a7d21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8051,6 +8051,7 @@ dependencies = [ "pallet-evm", "pallet-evm-precompile-dispatch", "pallet-timestamp", + "pallet-utility", "parity-scale-codec", "precompile-utils", "scale-info", diff --git a/pallets/unified-accounts/src/weights.rs b/pallets/unified-accounts/src/weights.rs index 4d87c151a2..40837093f2 100644 --- a/pallets/unified-accounts/src/weights.rs +++ b/pallets/unified-accounts/src/weights.rs @@ -96,7 +96,7 @@ impl WeightInfo for SubstrateWeight { fn to_account_id() -> Weight { // Proof Size summary in bytes: // Measured: `170` - // Estimated: `3533` + // Estimated: ` 3533` // Minimum execution time: 5_478_000 picoseconds. Weight::from_parts(5_661_000, 3533) .saturating_add(T::DbWeight::get().reads(1_u64)) diff --git a/precompiles/dispatch-lockdrop/Cargo.toml b/precompiles/dispatch-lockdrop/Cargo.toml index ac58f7ffea..6a297c0117 100644 --- a/precompiles/dispatch-lockdrop/Cargo.toml +++ b/precompiles/dispatch-lockdrop/Cargo.toml @@ -28,6 +28,7 @@ ethers = { workspace = true } frame-system = { workspace = true } pallet-balances = { workspace = true } pallet-timestamp = { workspace = true } +pallet-utility = { workspace = true } precompile-utils = { workspace = true, features = ["testing"] } scale-info = { workspace = true } sp-core = { workspace = true } diff --git a/precompiles/dispatch-lockdrop/DispatchLockdrop.sol b/precompiles/dispatch-lockdrop/DispatchLockdrop.sol new file mode 100644 index 0000000000..c2b50470d9 --- /dev/null +++ b/precompiles/dispatch-lockdrop/DispatchLockdrop.sol @@ -0,0 +1,20 @@ +pragma solidity ^0.8.0; + +/** + * @title Dispatch Lockdrop calls interface. + */ + +/// Interface to dispatch lockdrop calls precompiled contract +/// PRedeployed at the address 0x0000000000000000000000000000000000005007 +interface RescueLockdrop { + /** + * @dev Dispatch lockdrop call + * @param call - SCALE-encoded call arguments + * @param pubkey - full ECDSA pubkey 64 bytes + * @return boolean confirming whether the call got successfully dispatched + */ + function dispatch_lockdrop_call( + bytes call, + bytes pubkey + ) external returns (bool); +} \ No newline at end of file diff --git a/precompiles/dispatch-lockdrop/src/lib.rs b/precompiles/dispatch-lockdrop/src/lib.rs index ff08bd922e..f7b7d0fd6f 100644 --- a/precompiles/dispatch-lockdrop/src/lib.rs +++ b/precompiles/dispatch-lockdrop/src/lib.rs @@ -88,31 +88,43 @@ where let call = Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*input) .map_err(|_| revert("could not decode call"))?; + // 2. Check if dispatching the call will not exceed the gas limit + let mut gas_limit = handle.remaining_gas(); + // If caller specified a gas limit, make sure it's not exceeded. + if let Some(user_limit) = handle.gas_limit() { + gas_limit = gas_limit.min(user_limit); + } + let info = call.get_dispatch_info(); - handle - .record_external_cost(Some(info.weight.ref_time()), Some(info.weight.proof_size()))?; - // 2. Ensure that the caller matches the public key - if caller != Self::get_evm_address_from_pubkey(pubkey.as_bytes()) { - let message: &str = "caller does not match the public key"; - log::trace!(target: LOG_TARGET, "{}", message); + // Charge the weight of the call to dispatch AND the overhead weight + // corresponding to the blake2b Hash and the keccak256 Hash + // based on the weight of UA::claim_default_evm_address() + let weight = info.weight.ref_time().saturating_add(40_000_000u64); + if !(weight <= Runtime::GasWeightMapping::gas_to_weight(gas_limit, false).ref_time()) { return Err(PrecompileFailure::Error { - exit_status: ExitError::Other(message.into()), + exit_status: ExitError::OutOfGas, }); } - // 3. Derive the AccountId from the ECDSA compressed Public key - let origin = Self::get_account_id_from_pubkey(pubkey.as_bytes()) - .ok_or_else(|| revert("could not derive AccountId from pubkey"))?; + handle.record_external_cost(Some(weight), Some(info.weight.proof_size()))?; - // 4. validate the call - if let Some(err) = DispatchValidator::validate_before_dispatch(&origin, &call) { - let message: &str = "Error: could not validate call"; + // 3. Ensure that the caller matches the public key + if caller != Self::get_evm_address_from_pubkey(pubkey.as_bytes()) { + let message: &str = "caller does not match the public key"; log::trace!(target: LOG_TARGET, "{}", message); - return Err(err); + return Err(revert(message)); } - // 5. Dispatch the call + // 4. Derive the AccountId from the ECDSA compressed Public key + let origin = Self::get_account_id_from_pubkey(pubkey.as_bytes()) + .ok_or(revert("could not derive AccountId from pubkey"))?; + + // 5. validate the call + DispatchValidator::validate_before_dispatch(&origin, &call) + .map_or_else(|| Ok(()), |_| Err(revert("could not validate call")))?; + + // 6. Dispatch the call match call.dispatch(Some(origin).into()) { Ok(post_info) => { if post_info.pays_fee(&info) == Pays::Yes { @@ -138,11 +150,10 @@ where } Err(e) => { log::trace!(target: LOG_TARGET, "{:?}", e); - Err(PrecompileFailure::Error { - exit_status: ExitError::Other( - format!("dispatch execution failed: {}", <&'static str>::from(e)).into(), - ), - }) + Err(revert(format!( + "dispatch execution failed: {}", + <&'static str>::from(e) + ))) } } } diff --git a/precompiles/dispatch-lockdrop/src/mock.rs b/precompiles/dispatch-lockdrop/src/mock.rs index 3f3a2aba7b..e830752153 100644 --- a/precompiles/dispatch-lockdrop/src/mock.rs +++ b/precompiles/dispatch-lockdrop/src/mock.rs @@ -87,7 +87,9 @@ pub struct WhitelistedCalls; impl Contains for WhitelistedCalls { fn contains(call: &RuntimeCall) -> bool { match call { - RuntimeCall::Balances(_) => true, + RuntimeCall::Balances(pallet_balances::Call::transfer { .. }) => true, + RuntimeCall::System(frame_system::Call::remark { .. }) => true, + RuntimeCall::Utility(_) => true, _ => false, } } @@ -197,6 +199,13 @@ impl pallet_evm::Config for TestRuntime { type GasLimitPovSizeRatio = ConstU64<4>; } +impl pallet_utility::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; + type WeightInfo = (); +} + parameter_types! { // 2 storage items with value size 20 and 32 pub const AccountMappingStorageFee: u128 = 0; @@ -214,6 +223,7 @@ construct_runtime!( Evm: pallet_evm, Balances : pallet_balances, Timestamp: pallet_timestamp, + Utility: pallet_utility, } ); diff --git a/precompiles/dispatch-lockdrop/src/tests.rs b/precompiles/dispatch-lockdrop/src/tests.rs index a1141cdb46..e3306d947b 100644 --- a/precompiles/dispatch-lockdrop/src/tests.rs +++ b/precompiles/dispatch-lockdrop/src/tests.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . -use fp_evm::ExitError; +use core::str::from_utf8; use frame_support::traits::Currency; use sp_core::crypto::AccountId32; @@ -98,15 +98,86 @@ fn pubkey_does_not_match_caller_address() { }, ) .expect_no_logs() - .execute_error(ExitError::Other( - "caller does not match the public key".into(), - )); + .execute_reverts(|output| output == b"caller does not match the public key"); // Get Balance of ALICE in pallet balances and ensure it has not received any funds assert_eq!(Balances::free_balance(ALICE), 0); }); } +#[test] +fn decode_limit_too_high() { + ExtBuilder::default().build().execute_with(|| { + let mut nested_call = + RuntimeCall::System(frame_system::Call::remark { remark: Vec::new() }); + + // More than 8 depth + for _ in 0..9 { + nested_call = RuntimeCall::Utility(pallet_utility::Call::as_derivative { + index: 0, + call: Box::new(nested_call), + }); + } + + // Get Alice EVM address based on the Public Key + let alice_eth = crate::tests::eth_address(&alice_secret()); + // Get derived AccountId from the Blake2b hash of the compressed ECDSA Public key + let account_id = account_id(&alice_secret()); + // Fund this account (fund the lockdrop account) + let _ = Balances::deposit_creating(&account_id, ONE * 20); + // Get the full 64 bytes ECDSA Public key + let pubkey = crate::tests::public_key_full(&alice_secret()); + + precompiles() + .prepare_test( + alice_eth, + PRECOMPILE_ADDRESS, + PrecompileCall::dispatch_lockdrop_call { + call: nested_call.encode().into(), + pubkey: pubkey.into(), + }, + ) + .expect_no_logs() + .execute_reverts(|output| from_utf8(output).unwrap().contains("could not decode call")); + }); +} + +#[test] +fn decode_limit_ok() { + ExtBuilder::default().build().execute_with(|| { + let mut nested_call = + RuntimeCall::System(frame_system::Call::remark { remark: Vec::new() }); + + for _ in 0..8 { + nested_call = RuntimeCall::Utility(pallet_utility::Call::as_derivative { + index: 0, + call: Box::new(nested_call), + }); + } + + // Get Alice EVM address based on the Public Key + let alice_eth = crate::tests::eth_address(&alice_secret()); + // Get derived AccountId from the Blake2b hash of the compressed ECDSA Public key + let account_id = account_id(&alice_secret()); + // Fund this account (fund the lockdrop account) + let _ = Balances::deposit_creating(&account_id, ONE * 20); + // Get the full 64 bytes ECDSA Public key + let pubkey = crate::tests::public_key_full(&alice_secret()); + + precompiles() + .prepare_test( + alice_eth, + PRECOMPILE_ADDRESS, + PrecompileCall::dispatch_lockdrop_call { + call: nested_call.encode().into(), + pubkey: pubkey.into(), + }, + ) + .expect_no_logs() + .execute_returns(true); + }); +} + #[test] fn only_whitelisted_calls_can_be_dispatched() { ExtBuilder::default().build().execute_with(|| { @@ -134,7 +205,7 @@ fn only_whitelisted_calls_can_be_dispatched() { }, ) .expect_no_logs() - .execute_error(ExitError::Other("call filtered out".into())) + .execute_reverts(|output| output == b"could not validate call"); }); } diff --git a/runtime/local/src/precompiles.rs b/runtime/local/src/precompiles.rs index fb779533f1..d2a299127d 100644 --- a/runtime/local/src/precompiles.rs +++ b/runtime/local/src/precompiles.rs @@ -20,7 +20,7 @@ use crate::{RuntimeCall, UnifiedAccounts}; use astar_primitives::precompiles::DispatchFilterValidate; -use frame_support::pallet_prelude::ConstU32; +use frame_support::traits::ConstU32; use frame_support::{parameter_types, traits::Contains}; use pallet_evm_precompile_assets_erc20::Erc20AssetsPrecompileSet; use pallet_evm_precompile_blake2::Blake2F; @@ -77,7 +77,13 @@ impl Contains for WhitelistedLockdropCalls { | RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) => calls .iter() .all(|call| WhitelistedLockdropCalls::contains(call)), - RuntimeCall::DappsStaking(_) => true, + RuntimeCall::DappStaking(pallet_dapp_staking_v3::Call::unbond_and_unstake { + .. + }) => true, + RuntimeCall::DappStaking(pallet_dapp_staking_v3::Call::withdraw_unbonded { + .. + }) => true, + RuntimeCall::Balances(pallet_balances::Call::transfer { .. }) => true, RuntimeCall::Assets(pallet_assets::Call::transfer { .. }) => true, _ => false, } diff --git a/runtime/shibuya/src/precompiles.rs b/runtime/shibuya/src/precompiles.rs index 1f6dacaebb..8153f03146 100644 --- a/runtime/shibuya/src/precompiles.rs +++ b/runtime/shibuya/src/precompiles.rs @@ -20,7 +20,7 @@ use crate::{RuntimeCall, UnifiedAccounts, Xvm}; use astar_primitives::precompiles::DispatchFilterValidate; -use frame_support::pallet_prelude::ConstU32; +use frame_support::traits::ConstU32; use frame_support::{parameter_types, traits::Contains}; use pallet_evm_precompile_assets_erc20::Erc20AssetsPrecompileSet; use pallet_evm_precompile_blake2::Blake2F; @@ -78,7 +78,13 @@ impl Contains for WhitelistedLockdropCalls { | RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) => calls .iter() .all(|call| WhitelistedLockdropCalls::contains(call)), - RuntimeCall::DappStaking(_) => true, + RuntimeCall::DappStaking(pallet_dapp_staking_v3::Call::unbond_and_unstake { + .. + }) => true, + RuntimeCall::DappStaking(pallet_dapp_staking_v3::Call::withdraw_unbonded { + .. + }) => true, + RuntimeCall::Balances(pallet_balances::Call::transfer { .. }) => true, RuntimeCall::Assets(pallet_assets::Call::transfer { .. }) => true, _ => false, } From 357cb1670ab5e88371b6366ff8bac12cbe96ed50 Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Wed, 31 Jan 2024 14:58:23 +0100 Subject: [PATCH 15/20] fmt --- pallets/unified-accounts/src/weights.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/unified-accounts/src/weights.rs b/pallets/unified-accounts/src/weights.rs index 40837093f2..4d87c151a2 100644 --- a/pallets/unified-accounts/src/weights.rs +++ b/pallets/unified-accounts/src/weights.rs @@ -96,7 +96,7 @@ impl WeightInfo for SubstrateWeight { fn to_account_id() -> Weight { // Proof Size summary in bytes: // Measured: `170` - // Estimated: ` 3533` + // Estimated: `3533` // Minimum execution time: 5_478_000 picoseconds. Weight::from_parts(5_661_000, 3533) .saturating_add(T::DbWeight::get().reads(1_u64)) From 304a56cc47b45d699767ba5f2cb1a7800d32670e Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Thu, 1 Feb 2024 09:01:53 +0100 Subject: [PATCH 16/20] fixed weight --- precompiles/dispatch-lockdrop/src/lib.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/precompiles/dispatch-lockdrop/src/lib.rs b/precompiles/dispatch-lockdrop/src/lib.rs index f7b7d0fd6f..9166e0c460 100644 --- a/precompiles/dispatch-lockdrop/src/lib.rs +++ b/precompiles/dispatch-lockdrop/src/lib.rs @@ -106,8 +106,7 @@ where exit_status: ExitError::OutOfGas, }); } - - handle.record_external_cost(Some(weight), Some(info.weight.proof_size()))?; + handle.record_external_cost(Some(weight), None)?; // 3. Ensure that the caller matches the public key if caller != Self::get_evm_address_from_pubkey(pubkey.as_bytes()) { @@ -130,7 +129,7 @@ where if post_info.pays_fee(&info) == Pays::Yes { let actual_weight = post_info.actual_weight.unwrap_or(info.weight); let cost = Runtime::GasWeightMapping::weight_to_gas(actual_weight); - handle.record_cost(cost)?; + handle.record_external_cost(None, Some(info.weight.proof_size()))?; handle.refund_external_cost( Some( @@ -138,11 +137,7 @@ where .ref_time() .saturating_sub(actual_weight.ref_time()), ), - Some( - info.weight - .proof_size() - .saturating_sub(actual_weight.proof_size()), - ), + None, ); } From c625065d53a7fadf3fc39eaa6f6b0083b782f42e Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Thu, 1 Feb 2024 09:23:58 +0100 Subject: [PATCH 17/20] handle gas limit --- precompiles/dispatch-lockdrop/src/lib.rs | 28 ++++++++++-------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/precompiles/dispatch-lockdrop/src/lib.rs b/precompiles/dispatch-lockdrop/src/lib.rs index 9166e0c460..ca2f61923a 100644 --- a/precompiles/dispatch-lockdrop/src/lib.rs +++ b/precompiles/dispatch-lockdrop/src/lib.rs @@ -81,6 +81,7 @@ where pubkey ); + let target_gas = handle.gas_limit(); let caller: H160 = handle.context().caller.into(); let input: Vec = call.into(); @@ -88,23 +89,19 @@ where let call = Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*input) .map_err(|_| revert("could not decode call"))?; - // 2. Check if dispatching the call will not exceed the gas limit - let mut gas_limit = handle.remaining_gas(); - // If caller specified a gas limit, make sure it's not exceeded. - if let Some(user_limit) = handle.gas_limit() { - gas_limit = gas_limit.min(user_limit); - } - + // 2. Charge the max amount of weight ref_time and + // later when call is successfully dispatched, + // charge proof_size and refund the ref_time difference. + // Note: adding hard coded ref_time corresponding to the blake2b Hash + // and the keccak256 Hash based on the weight of UA::claim_default_evm_address() let info = call.get_dispatch_info(); - - // Charge the weight of the call to dispatch AND the overhead weight - // corresponding to the blake2b Hash and the keccak256 Hash - // based on the weight of UA::claim_default_evm_address() let weight = info.weight.ref_time().saturating_add(40_000_000u64); - if !(weight <= Runtime::GasWeightMapping::gas_to_weight(gas_limit, false).ref_time()) { - return Err(PrecompileFailure::Error { - exit_status: ExitError::OutOfGas, - }); + if let Some(gas) = target_gas { + if !(weight <= Runtime::GasWeightMapping::gas_to_weight(gas, false).ref_time()) { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } } handle.record_external_cost(Some(weight), None)?; @@ -128,7 +125,6 @@ where Ok(post_info) => { if post_info.pays_fee(&info) == Pays::Yes { let actual_weight = post_info.actual_weight.unwrap_or(info.weight); - let cost = Runtime::GasWeightMapping::weight_to_gas(actual_weight); handle.record_external_cost(None, Some(info.weight.proof_size()))?; handle.refund_external_cost( From 98ec7c9e9cb44e8255d8f9ec6fe5cb14bd023e5d Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Fri, 2 Feb 2024 15:16:13 +0100 Subject: [PATCH 18/20] PR review --- precompiles/dispatch-lockdrop/src/lib.rs | 73 ++++++------------------ precompiles/utils/src/substrate.rs | 19 ++++++ 2 files changed, 38 insertions(+), 54 deletions(-) diff --git a/precompiles/dispatch-lockdrop/src/lib.rs b/precompiles/dispatch-lockdrop/src/lib.rs index ca2f61923a..b06af0582f 100644 --- a/precompiles/dispatch-lockdrop/src/lib.rs +++ b/precompiles/dispatch-lockdrop/src/lib.rs @@ -20,11 +20,11 @@ extern crate alloc; -use alloc::format; use core::marker::PhantomData; -use fp_evm::{ExitError, PrecompileFailure, PrecompileHandle}; +use fp_evm::PrecompileHandle; use frame_support::pallet_prelude::IsType; -use frame_support::{codec::DecodeLimit as _, dispatch::Pays, traits::Get}; +use frame_support::weights::Weight; +use frame_support::{codec::DecodeLimit as _, traits::Get}; use frame_support::{ dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, traits::ConstU32, @@ -32,7 +32,7 @@ use frame_support::{ use frame_system::Config; use pallet_evm::GasWeightMapping; use pallet_evm_precompile_dispatch::DispatchValidateT; -use precompile_utils::prelude::{revert, BoundedBytes, UnboundedBytes}; +use precompile_utils::prelude::{revert, BoundedBytes, RuntimeHelper, UnboundedBytes}; use precompile_utils::EvmResult; use sp_core::{crypto::AccountId32, H160, H256}; use sp_io::hashing::keccak_256; @@ -81,72 +81,37 @@ where pubkey ); - let target_gas = handle.gas_limit(); let caller: H160 = handle.context().caller.into(); let input: Vec = call.into(); - // 1. Decode the call - let call = Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*input) - .map_err(|_| revert("could not decode call"))?; + // Record the cost of the call to ensure there is no free execution + handle.record_cost(Runtime::GasWeightMapping::weight_to_gas( + Weight::from_parts(1_000_000u64, 0), + ))?; - // 2. Charge the max amount of weight ref_time and - // later when call is successfully dispatched, - // charge proof_size and refund the ref_time difference. - // Note: adding hard coded ref_time corresponding to the blake2b Hash - // and the keccak256 Hash based on the weight of UA::claim_default_evm_address() - let info = call.get_dispatch_info(); - let weight = info.weight.ref_time().saturating_add(40_000_000u64); - if let Some(gas) = target_gas { - if !(weight <= Runtime::GasWeightMapping::gas_to_weight(gas, false).ref_time()) { - return Err(PrecompileFailure::Error { - exit_status: ExitError::OutOfGas, - }); - } - } - handle.record_external_cost(Some(weight), None)?; - - // 3. Ensure that the caller matches the public key + // Ensure that the caller matches the public key if caller != Self::get_evm_address_from_pubkey(pubkey.as_bytes()) { let message: &str = "caller does not match the public key"; log::trace!(target: LOG_TARGET, "{}", message); return Err(revert(message)); } - // 4. Derive the AccountId from the ECDSA compressed Public key + // Derive the account id from the public key let origin = Self::get_account_id_from_pubkey(pubkey.as_bytes()) .ok_or(revert("could not derive AccountId from pubkey"))?; - // 5. validate the call + // Decode the call + let call = Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*input) + .map_err(|_| revert("could not decode call"))?; + + // Validate the call - ensure that the call is allowed in filter DispatchValidator::validate_before_dispatch(&origin, &call) .map_or_else(|| Ok(()), |_| Err(revert("could not validate call")))?; - // 6. Dispatch the call - match call.dispatch(Some(origin).into()) { - Ok(post_info) => { - if post_info.pays_fee(&info) == Pays::Yes { - let actual_weight = post_info.actual_weight.unwrap_or(info.weight); - handle.record_external_cost(None, Some(info.weight.proof_size()))?; - - handle.refund_external_cost( - Some( - info.weight - .ref_time() - .saturating_sub(actual_weight.ref_time()), - ), - None, - ); - } - - Ok(true) - } - Err(e) => { - log::trace!(target: LOG_TARGET, "{:?}", e); - Err(revert(format!( - "dispatch execution failed: {}", - <&'static str>::from(e) - ))) - } - } + // Dispatch the call and handle the cost + RuntimeHelper::::try_dispatch_runtime_call(handle, Some(origin).into(), call)?; + + Ok(true) } fn get_account_id_from_pubkey(pubkey: &[u8]) -> Option<::AccountId> { diff --git a/precompiles/utils/src/substrate.rs b/precompiles/utils/src/substrate.rs index 5873afaaac..a621745dac 100644 --- a/precompiles/utils/src/substrate.rs +++ b/precompiles/utils/src/substrate.rs @@ -112,6 +112,25 @@ where Runtime::RuntimeCall: From, { let call = Runtime::RuntimeCall::from(call); + Self::do_try_dispatch(handle, origin, call) + } + + /// Try to dispatch a Runtime call. + /// Return an error if there are not enough gas, or if the call fails. + /// If successful returns the used gas using the Runtime GasWeightMapping. + pub fn try_dispatch_runtime_call( + handle: &mut impl PrecompileHandle, + origin: ::RuntimeOrigin, + call: Runtime::RuntimeCall, + ) -> Result { + Self::do_try_dispatch(handle, origin, call) + } + + fn do_try_dispatch( + handle: &mut impl PrecompileHandle, + origin: ::RuntimeOrigin, + call: Runtime::RuntimeCall, + ) -> Result { let dispatch_info = call.get_dispatch_info(); Self::record_weight_v2_cost(handle, dispatch_info.weight).map_err(TryDispatchError::Evm)?; From c792797009240f9b566578d5c4701a2f7d196230 Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Fri, 2 Feb 2024 16:33:44 +0100 Subject: [PATCH 19/20] fmt --- precompiles/dispatch-lockdrop/src/lib.rs | 6 +++++- precompiles/utils/src/substrate.rs | 19 ------------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/precompiles/dispatch-lockdrop/src/lib.rs b/precompiles/dispatch-lockdrop/src/lib.rs index b06af0582f..fa04775f6a 100644 --- a/precompiles/dispatch-lockdrop/src/lib.rs +++ b/precompiles/dispatch-lockdrop/src/lib.rs @@ -109,7 +109,11 @@ where .map_or_else(|| Ok(()), |_| Err(revert("could not validate call")))?; // Dispatch the call and handle the cost - RuntimeHelper::::try_dispatch_runtime_call(handle, Some(origin).into(), call)?; + RuntimeHelper::::try_dispatch::( + handle, + Some(origin).into(), + call, + )?; Ok(true) } diff --git a/precompiles/utils/src/substrate.rs b/precompiles/utils/src/substrate.rs index a621745dac..5873afaaac 100644 --- a/precompiles/utils/src/substrate.rs +++ b/precompiles/utils/src/substrate.rs @@ -112,25 +112,6 @@ where Runtime::RuntimeCall: From, { let call = Runtime::RuntimeCall::from(call); - Self::do_try_dispatch(handle, origin, call) - } - - /// Try to dispatch a Runtime call. - /// Return an error if there are not enough gas, or if the call fails. - /// If successful returns the used gas using the Runtime GasWeightMapping. - pub fn try_dispatch_runtime_call( - handle: &mut impl PrecompileHandle, - origin: ::RuntimeOrigin, - call: Runtime::RuntimeCall, - ) -> Result { - Self::do_try_dispatch(handle, origin, call) - } - - fn do_try_dispatch( - handle: &mut impl PrecompileHandle, - origin: ::RuntimeOrigin, - call: Runtime::RuntimeCall, - ) -> Result { let dispatch_info = call.get_dispatch_info(); Self::record_weight_v2_cost(handle, dispatch_info.weight).map_err(TryDispatchError::Evm)?; From 3605e753439dba01db0df7d33646ea40fc176dff Mon Sep 17 00:00:00 2001 From: PierreOssun Date: Mon, 5 Feb 2024 17:05:03 +0100 Subject: [PATCH 20/20] Pr comments and added tests --- Cargo.lock | 1 + precompiles/dispatch-lockdrop/Cargo.toml | 1 + .../dispatch-lockdrop/DispatchLockdrop.sol | 6 +- precompiles/dispatch-lockdrop/src/lib.rs | 6 +- precompiles/dispatch-lockdrop/src/tests.rs | 94 ++++++++++++++++++- 5 files changed, 99 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3158a7d21..4d600a1b5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8045,6 +8045,7 @@ dependencies = [ "fp-evm", "frame-support", "frame-system", + "hex-literal", "libsecp256k1", "log", "pallet-balances", diff --git a/precompiles/dispatch-lockdrop/Cargo.toml b/precompiles/dispatch-lockdrop/Cargo.toml index 6a297c0117..b2ca7f5e20 100644 --- a/precompiles/dispatch-lockdrop/Cargo.toml +++ b/precompiles/dispatch-lockdrop/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true fp-evm = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +hex-literal = { workspace = true } libsecp256k1 = { workspace = true, features = ["hmac", "static-context"] } log = { workspace = true } pallet-evm = { workspace = true } diff --git a/precompiles/dispatch-lockdrop/DispatchLockdrop.sol b/precompiles/dispatch-lockdrop/DispatchLockdrop.sol index c2b50470d9..f464b5c3da 100644 --- a/precompiles/dispatch-lockdrop/DispatchLockdrop.sol +++ b/precompiles/dispatch-lockdrop/DispatchLockdrop.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; */ /// Interface to dispatch lockdrop calls precompiled contract -/// PRedeployed at the address 0x0000000000000000000000000000000000005007 +/// Pre-deployed at the address 0x0000000000000000000000000000000000005007 interface RescueLockdrop { /** * @dev Dispatch lockdrop call @@ -14,7 +14,7 @@ interface RescueLockdrop { * @return boolean confirming whether the call got successfully dispatched */ function dispatch_lockdrop_call( - bytes call, - bytes pubkey + bytes calldata call, + bytes calldata pubkey ) external returns (bool); } \ No newline at end of file diff --git a/precompiles/dispatch-lockdrop/src/lib.rs b/precompiles/dispatch-lockdrop/src/lib.rs index fa04775f6a..ee38bd0938 100644 --- a/precompiles/dispatch-lockdrop/src/lib.rs +++ b/precompiles/dispatch-lockdrop/src/lib.rs @@ -84,9 +84,9 @@ where let caller: H160 = handle.context().caller.into(); let input: Vec = call.into(); - // Record the cost of the call to ensure there is no free execution + // Record a fixed amount of weight to ensure there is no free execution handle.record_cost(Runtime::GasWeightMapping::weight_to_gas( - Weight::from_parts(1_000_000u64, 0), + Weight::from_parts(1_000_000_000u64, 0), ))?; // Ensure that the caller matches the public key @@ -106,7 +106,7 @@ where // Validate the call - ensure that the call is allowed in filter DispatchValidator::validate_before_dispatch(&origin, &call) - .map_or_else(|| Ok(()), |_| Err(revert("could not validate call")))?; + .map_or_else(|| Ok(()), |_| Err(revert("invalid Call")))?; // Dispatch the call and handle the cost RuntimeHelper::::try_dispatch::( diff --git a/precompiles/dispatch-lockdrop/src/tests.rs b/precompiles/dispatch-lockdrop/src/tests.rs index e3306d947b..5a9cd53624 100644 --- a/precompiles/dispatch-lockdrop/src/tests.rs +++ b/precompiles/dispatch-lockdrop/src/tests.rs @@ -17,15 +17,18 @@ // along with Astar. If not, see . use core::str::from_utf8; +use frame_support::dispatch::GetDispatchInfo; use frame_support::traits::Currency; -use sp_core::crypto::AccountId32; +use libsecp256k1::PublicKeyFormat; +use sp_core::crypto::{AccountId32, Ss58Codec}; use crate::mock::*; use astar_primitives::evm::EvmAddress; +use hex_literal::hex; use parity_scale_codec::Encode; use precompile_utils::testing::*; -use sp_core::ecdsa; +use sp_core::{ecdsa, Pair}; fn precompiles() -> TestPrecompileSet { PrecompilesValue::get() @@ -68,6 +71,42 @@ fn dispatch_calls_on_behalf_of_lockdrop_works() { }); } +#[test] +fn proper_gas_is_charged() { + ExtBuilder::default().build().execute_with(|| { + let call = RuntimeCall::Balances(pallet_balances::Call::transfer { + dest: ALICE, + value: 15 * ONE, + }); + + // Dispatch a call and ensure gas is charged properly + // Expected gas is the constant weight of 1_000_000_000 and the weight of the call + // In mock one unit of ref_time us charged 1 + let expected_gas = 1_000_000_000u64 + call.get_dispatch_info().weight.ref_time(); + + // Get Alice EVM address based on the Public Key + let alice_eth = crate::tests::eth_address(&alice_secret()); + // Get derived AccountId from the Blake2b hash of the compressed ECDSA Public key + let account_id = account_id(&alice_secret()); + // Fund this account (fund the lockdrop account) + let _ = Balances::deposit_creating(&account_id, ONE * 20); + // Get the full 64 bytes ECDSA Public key + let pubkey = crate::tests::public_key_full(&alice_secret()); + + precompiles() + .prepare_test( + alice_eth, + PRECOMPILE_ADDRESS, + PrecompileCall::dispatch_lockdrop_call { + call: call.encode().into(), + pubkey: pubkey.into(), + }, + ) + .expect_cost(expected_gas) + .execute_returns(true); + }); +} + #[test] fn pubkey_does_not_match_caller_address() { ExtBuilder::default().build().execute_with(|| { @@ -105,6 +144,47 @@ fn pubkey_does_not_match_caller_address() { }); } +#[test] +fn pubkey_derive_to_proper_ss58() { + ExtBuilder::default().build().execute_with(|| { + // Transfer balance to Alice + let call = RuntimeCall::Balances(pallet_balances::Call::transfer { + dest: ALICE, + value: 15 * ONE, + }); + // Sanity check - Alice holds no Balance + assert_eq!(Balances::free_balance(ALICE), 0); + + // The seed "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + // should resolve to the SS58 address "5EGynCAEvv8NLeHx8vDMvb8hTcEcMYUMWCDQEEncNEfNWB2W" + // If we fund this account, it will be able to dispatch the Transfer call + let pair = ecdsa::Pair::from_seed(&hex!( + "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + )); + let pubkey = crate::tests::public_key_full_from_compressed(pair.public().as_ref()); + let alice_eth = EvmAddress::from_slice(&sp_io::hashing::keccak_256(&pubkey)[12..]); + let account_id = + AccountId::from_ss58check("5EGynCAEvv8NLeHx8vDMvb8hTcEcMYUMWCDQEEncNEfNWB2W").unwrap(); + // Fund this account + let _ = Balances::deposit_creating(&account_id, ONE * 20); + + precompiles() + .prepare_test( + alice_eth, + PRECOMPILE_ADDRESS, + PrecompileCall::dispatch_lockdrop_call { + call: call.encode().into(), + pubkey: pubkey.into(), + }, + ) + .expect_no_logs() + .execute_returns(true); + + // Assert that the call (Transfer) was successful + assert_eq!(Balances::free_balance(ALICE), 15 * ONE); + }); +} + #[test] fn decode_limit_too_high() { ExtBuilder::default().build().execute_with(|| { @@ -205,7 +285,7 @@ fn only_whitelisted_calls_can_be_dispatched() { }, ) .expect_no_logs() - .execute_reverts(|output| output == b"could not validate call"); + .execute_reverts(|output| output == b"invalid Call"); }); } @@ -228,6 +308,14 @@ fn eth_address(secret: &libsecp256k1::SecretKey) -> EvmAddress { ) } +fn public_key_full_from_compressed(pubkey: &[u8]) -> [u8; 64] { + libsecp256k1::PublicKey::parse_slice(pubkey, Some(PublicKeyFormat::Compressed)) + .unwrap() + .serialize()[1..65] + .try_into() + .unwrap() +} + fn public_key_full(secret: &libsecp256k1::SecretKey) -> [u8; 64] { libsecp256k1::PublicKey::from_secret_key(secret).serialize()[1..65] .try_into()