Skip to content

Refactor precompiles #1362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 4 additions & 14 deletions precompiles/src/balance_transfer.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
use core::marker::PhantomData;

use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo};
use frame_system::RawOrigin;
use pallet_evm::PrecompileHandle;
use precompile_utils::EvmResult;
use sp_core::{H256, U256};
use sp_runtime::traits::{Dispatchable, StaticLookup, UniqueSaturatedInto};

use crate::parser::{contract_to_origin, parse_pubkey};
use crate::{PrecompileExt, PrecompileHandleExt};

pub(crate) struct BalanceTransferPrecompile<R>(PhantomData<R>);

impl<R> PrecompileExt for BalanceTransferPrecompile<R>
impl<R> PrecompileExt<R::AccountId> for BalanceTransferPrecompile<R>
where
R: frame_system::Config + pallet_balances::Config + pallet_evm::Config,
R::AccountId: From<[u8; 32]>,
Expand All @@ -24,11 +24,6 @@ where
<R as pallet_balances::Config>::Balance: TryFrom<U256>,
{
const INDEX: u64 = 2048;
const ADDRESS_SS58: Option<[u8; 32]> = Some([
0x07, 0xec, 0x71, 0x2a, 0x5d, 0x38, 0x43, 0x4d, 0xdd, 0x03, 0x3f, 0x8f, 0x02, 0x4e, 0xcd,
0xfc, 0x4b, 0xb5, 0x95, 0x1c, 0x13, 0xc3, 0x08, 0x5c, 0x39, 0x9c, 0x8a, 0x5f, 0x62, 0x93,
0x70, 0x5d,
]);
}

#[precompile_utils::precompile]
Expand All @@ -53,18 +48,13 @@ where
return Ok(());
}

let dest = parse_pubkey::<R::AccountId>(address.as_bytes())?.0.into();
let dest = R::AccountId::from(address.0).into();

let call = pallet_balances::Call::<R>::transfer_allow_death {
dest,
value: amount_sub.unique_saturated_into(),
};

handle.try_dispatch_runtime_call::<R, _>(
call,
contract_to_origin(
&Self::ADDRESS_SS58.expect("ADDRESS_SS58 is defined for BalanceTransferPrecompile"),
)?,
)
handle.try_dispatch_runtime_call::<R, _>(call, RawOrigin::Signed(Self::account_id()))
}
}
33 changes: 28 additions & 5 deletions precompiles/src/ed25519.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
extern crate alloc;

use alloc::vec::Vec;
use core::marker::PhantomData;

use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile, PrecompileFailure};

use crate::PrecompileExt;
use crate::parser::parse_slice;

pub(crate) struct Ed25519Verify;
pub(crate) struct Ed25519Verify<A>(PhantomData<A>);

impl PrecompileExt for Ed25519Verify {
impl<A> PrecompileExt<A> for Ed25519Verify<A>
where
A: From<[u8; 32]>,
{
const INDEX: u64 = 1026;
const ADDRESS_SS58: Option<[u8; 32]> = None;
}

impl LinearCostPrecompile for Ed25519Verify {
impl<A> LinearCostPrecompile for Ed25519Verify<A>
where
A: From<[u8; 32]>,
{
const BASE: u64 = 15;
const WORD: u64 = 3;

Expand Down Expand Up @@ -47,3 +52,21 @@ impl LinearCostPrecompile for Ed25519Verify {
Ok((ExitSucceed::Returned, buf.to_vec()))
}
}

/// Takes a slice from bytes with PrecompileFailure as Error
fn parse_slice(data: &[u8], from: usize, to: usize) -> Result<&[u8], PrecompileFailure> {
let maybe_slice = data.get(from..to);
if let Some(slice) = maybe_slice {
Ok(slice)
} else {
log::error!(
"fail to get slice from data, {:?}, from {}, to {}",
&data,
from,
to
);
Err(PrecompileFailure::Error {
exit_status: ExitError::InvalidRange,
})
}
}
155 changes: 155 additions & 0 deletions precompiles/src/extensions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
extern crate alloc;

use alloc::format;

use frame_support::dispatch::{GetDispatchInfo, Pays, PostDispatchInfo};
use frame_system::RawOrigin;
use pallet_evm::{
AddressMapping, BalanceConverter, ExitError, GasWeightMapping, PrecompileFailure,
PrecompileHandle,
};
use precompile_utils::EvmResult;
use sp_core::{H160, U256, blake2_256};
use sp_runtime::traits::Dispatchable;
use sp_std::vec::Vec;

pub(crate) trait PrecompileHandleExt: PrecompileHandle {
fn caller_account_id<R>(&self) -> R::AccountId
where
R: frame_system::Config + pallet_evm::Config,
<R as pallet_evm::Config>::AddressMapping: AddressMapping<R::AccountId>,
{
<R as pallet_evm::Config>::AddressMapping::into_account_id(self.context().caller)
}

fn try_convert_apparent_value<R>(&self) -> EvmResult<U256>
where
R: pallet_evm::Config,
{
let amount = self.context().apparent_value;
<R as pallet_evm::Config>::BalanceConverter::into_substrate_balance(amount).ok_or(
PrecompileFailure::Error {
exit_status: ExitError::Other(
"error converting balance from ETH to subtensor".into(),
),
},
)
}

/// Dispatches a runtime call, but also checks and records the gas costs.
fn try_dispatch_runtime_call<R, Call>(
&mut self,
call: Call,
origin: RawOrigin<R::AccountId>,
) -> EvmResult<()>
where
R: frame_system::Config + pallet_evm::Config,
R::RuntimeCall: From<Call>,
R::RuntimeCall: GetDispatchInfo + Dispatchable<PostInfo = PostDispatchInfo>,
R::RuntimeOrigin: From<RawOrigin<R::AccountId>>,
{
let call = R::RuntimeCall::from(call);
let info = GetDispatchInfo::get_dispatch_info(&call);

let target_gas = self.gas_limit();
if let Some(gas) = target_gas {
let valid_weight =
<R as pallet_evm::Config>::GasWeightMapping::gas_to_weight(gas, false).ref_time();
if info.weight.ref_time() > valid_weight {
return Err(PrecompileFailure::Error {
exit_status: ExitError::OutOfGas,
});
}
}

self.record_external_cost(
Some(info.weight.ref_time()),
Some(info.weight.proof_size()),
None,
)?;

match call.dispatch(R::RuntimeOrigin::from(origin)) {
Ok(post_info) => {
if post_info.pays_fee(&info) == Pays::Yes {
let actual_weight = post_info.actual_weight.unwrap_or(info.weight);
let cost =
<R as pallet_evm::Config>::GasWeightMapping::weight_to_gas(actual_weight);
self.record_cost(cost)?;

self.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()),
),
);
}

log::info!("Dispatch succeeded. Post info: {:?}", post_info);

Ok(())
}
Err(e) => {
log::error!("Dispatch failed. Error: {:?}", e);
log::warn!("Returning error PrecompileFailure::Error");
Err(PrecompileFailure::Error {
exit_status: ExitError::Other(
format!("dispatch execution failed: {}", <&'static str>::from(e)).into(),
),
})
}
}
}
}

impl<T> PrecompileHandleExt for T where T: PrecompileHandle {}

pub(crate) trait PrecompileExt<AccountId: From<[u8; 32]>> {
const INDEX: u64;

// ss58 public key i.e., the contract sends funds it received to the destination address from
// the method parameter.
fn account_id() -> AccountId {
let hash = H160::from_low_u64_be(Self::INDEX);
let prefix = b"evm:";

// Concatenate prefix and ethereum address
let mut combined = Vec::new();
combined.extend_from_slice(prefix);
combined.extend_from_slice(hash.as_bytes());

let hash = blake2_256(&combined);

hash.into()
}
}

#[cfg(test)]
mod test {
use super::*;

use sp_core::crypto::AccountId32;

#[test]
fn ss58_address_from_index_works() {
assert_eq!(
TestPrecompile::account_id(),
AccountId32::from([
0x3a, 0x86, 0x18, 0xfb, 0xbb, 0x1b, 0xbc, 0x47, 0x86, 0x64, 0xff, 0x53, 0x46, 0x18,
0x0c, 0x35, 0xd0, 0x9f, 0xac, 0x26, 0xf2, 0x02, 0x70, 0x85, 0xb3, 0x1c, 0x56, 0xc1,
0x06, 0x3c, 0x1c, 0xd3,
])
);
}

struct TestPrecompile;

impl PrecompileExt<AccountId32> for TestPrecompile {
const INDEX: u64 = 2051;
}
}
Loading
Loading