Skip to content

add subnets precompile #1110

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 31 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
bc8c514
add subnet precompile contract
open-junius Dec 10, 2024
157032b
remove commented code
open-junius Dec 10, 2024
5435ccd
refactor code
open-junius Dec 10, 2024
7169908
fix clippy
open-junius Dec 11, 2024
7155329
merge with target branch
open-junius Dec 11, 2024
cdda2f6
fix compile error
open-junius Dec 11, 2024
e565825
add memory storage
open-junius Dec 16, 2024
6a0f6f9
add subnets precompile
open-junius Dec 20, 2024
2141e00
Merge branch 'devnet-ready' into feat/subnet-precompile
open-junius Dec 20, 2024
2284e31
merge with subnet precompile
open-junius Dec 20, 2024
be5bc35
merge with target branch
open-junius Jan 16, 2025
1643238
fix compilation
open-junius Jan 16, 2025
5040e52
update sol
open-junius Jan 16, 2025
1341e7a
rename contract
open-junius Jan 17, 2025
23f9b87
fix e2e test
open-junius Jan 20, 2025
f3c6059
Merge branch 'devnet-ready' into neuron-precompile
open-junius Jan 20, 2025
9de1927
commit Cargo.lock
open-junius Jan 20, 2025
3d05f7b
cargo clippy
open-junius Jan 20, 2025
1a34d33
refactor helper
open-junius Jan 23, 2025
541518e
fix compilation error
open-junius Jan 23, 2025
d148c2b
refactor get pubkey method
open-junius Jan 23, 2025
96a1d08
cargo clippy
open-junius Jan 23, 2025
241d620
cargo fmt
open-junius Jan 23, 2025
a0819b4
remove byte_to_account_id
open-junius Jan 23, 2025
4a73af6
merge with devnet ready
open-junius Jan 24, 2025
682ac1a
Merge branch 'devnet-ready' into neuron-precompile
open-junius Jan 28, 2025
ae60405
merge with target branch
open-junius Jan 30, 2025
f644f26
refactor code
open-junius Jan 30, 2025
f25cabc
add len check
open-junius Jan 30, 2025
998867e
fix wrong origin
open-junius Jan 30, 2025
cdfffaf
fix clippy
open-junius Jan 30, 2025
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
8 changes: 3 additions & 5 deletions runtime/src/precompiles/balance_transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ use sp_core::U256;
use sp_runtime::traits::UniqueSaturatedInto;
use sp_std::vec;

use crate::precompiles::{bytes_to_account_id, get_method_id, get_slice};
use crate::precompiles::{get_method_id, get_pubkey, get_slice};
use crate::{Runtime, RuntimeCall};

pub const BALANCE_TRANSFER_INDEX: u64 = 2048;

// This is a hardcoded hashed address mapping of 0x0000000000000000000000000000000000000800 to an
// ss58 public key i.e., the contract sends funds it received to the destination address from the
// method parameter.
const CONTRACT_ADDRESS_SS58: [u8; 32] = [
Expand Down Expand Up @@ -52,8 +50,8 @@ impl BalanceTransferPrecompile {
}

let address_bytes_dst = get_slice(txdata, 4, 36)?;
let account_id_src = bytes_to_account_id(&CONTRACT_ADDRESS_SS58)?;
let account_id_dst = bytes_to_account_id(address_bytes_dst)?;
let (account_id_src, _) = get_pubkey(&CONTRACT_ADDRESS_SS58)?;
let (account_id_dst, _) = get_pubkey(address_bytes_dst)?;

let call = RuntimeCall::Balances(pallet_balances::Call::<Runtime>::transfer_allow_death {
dest: account_id_dst.into(),
Expand Down
133 changes: 117 additions & 16 deletions runtime/src/precompiles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,40 @@ use sp_core::{hashing::keccak_256, H160};
use sp_runtime::AccountId32;

use pallet_evm::{
ExitError, IsPrecompileResult, Precompile, PrecompileFailure, PrecompileHandle,
AddressMapping, BalanceConverter, ExitError, ExitSucceed, HashedAddressMapping,
IsPrecompileResult, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput,
PrecompileResult, PrecompileSet,
};
use pallet_evm_precompile_modexp::Modexp;
use pallet_evm_precompile_sha3fips::Sha3FIPS256;
use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256};

use frame_system::RawOrigin;

use sp_core::crypto::Ss58Codec;
use sp_core::U256;
use sp_runtime::traits::Dispatchable;
use sp_runtime::traits::{BlakeTwo256, UniqueSaturatedInto};

use sp_std::vec;

use crate::{Runtime, RuntimeCall};

// Include custom precompiles
mod balance_transfer;
mod ed25519;
mod metagraph;
mod neuron;
mod staking;
mod subnet;

use balance_transfer::*;
use ed25519::*;
use metagraph::*;
use neuron::*;
use staking::*;

use subnet::*;
pub struct FrontierPrecompiles<R>(PhantomData<R>);

impl<R> Default for FrontierPrecompiles<R>
where
R: pallet_evm::Config,
Expand All @@ -39,7 +53,7 @@ where
pub fn new() -> Self {
Self(Default::default())
}
pub fn used_addresses() -> [H160; 11] {
pub fn used_addresses() -> [H160; 13] {
[
hash(1),
hash(2),
Expand All @@ -51,7 +65,9 @@ where
hash(EDVERIFY_PRECOMPILE_INDEX),
hash(BALANCE_TRANSFER_INDEX),
hash(STAKING_PRECOMPILE_INDEX),
hash(SUBNET_PRECOMPILE_INDEX),
hash(METAGRAPH_PRECOMPILE_INDEX),
hash(NEURON_PRECOMPILE_INDEX),
]
}
}
Expand All @@ -76,9 +92,11 @@ where
Some(BalanceTransferPrecompile::execute(handle))
}
a if a == hash(STAKING_PRECOMPILE_INDEX) => Some(StakingPrecompile::execute(handle)),
a if a == hash(SUBNET_PRECOMPILE_INDEX) => Some(SubnetPrecompile::execute(handle)),
a if a == hash(METAGRAPH_PRECOMPILE_INDEX) => {
Some(MetagraphPrecompile::execute(handle))
}
a if a == hash(NEURON_PRECOMPILE_INDEX) => Some(NeuronPrecompile::execute(handle)),

_ => None,
}
Expand Down Expand Up @@ -106,27 +124,110 @@ pub fn get_method_id(method_signature: &str) -> [u8; 4] {
[hash[0], hash[1], hash[2], hash[3]]
}

/// Convert bytes to AccountId32 with PrecompileFailure as Error
/// which consumes all gas
///
pub fn bytes_to_account_id(account_id_bytes: &[u8]) -> Result<AccountId32, PrecompileFailure> {
AccountId32::try_from(account_id_bytes).map_err(|_| {
log::info!("Error parsing account id bytes {:?}", account_id_bytes);
PrecompileFailure::Error {
exit_status: ExitError::InvalidRange,
}
})
}

/// Takes a slice from bytes with PrecompileFailure as Error
///
pub fn get_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,
})
}
}

/// The function return the token to smart contract
fn transfer_back_to_caller(
smart_contract_address: &str,
account_id: &AccountId32,
amount: U256,
) -> Result<(), PrecompileFailure> {
// this is staking smart contract's(0x0000000000000000000000000000000000000801) sr25519 address
let smart_contract_account_id = match AccountId32::from_ss58check(smart_contract_address) {
// match AccountId32::from_ss58check("5CwnBK9Ack1mhznmCnwiibCNQc174pYQVktYW3ayRpLm4K2X") {
Ok(addr) => addr,
Err(_) => {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("Invalid SS58 address".into()),
});
}
};
let amount_sub =
<Runtime as pallet_evm::Config>::BalanceConverter::into_substrate_balance(amount)
.ok_or(ExitError::OutOfFund)?;

// Create a transfer call from the smart contract to the caller
let transfer_call =
RuntimeCall::Balances(pallet_balances::Call::<Runtime>::transfer_allow_death {
dest: account_id.clone().into(),
value: amount_sub.unique_saturated_into(),
});

// Execute the transfer
let transfer_result =
transfer_call.dispatch(RawOrigin::Signed(smart_contract_account_id).into());

if let Err(dispatch_error) = transfer_result {
log::error!(
"Transfer back to caller failed. Error: {:?}",
dispatch_error
);
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("Transfer back to caller failed".into()),
});
}

Ok(())
}

fn dispatch(
handle: &mut impl PrecompileHandle,
call: RuntimeCall,
smart_contract_address: &str,
) -> PrecompileResult {
let account_id =
<HashedAddressMapping<BlakeTwo256> as AddressMapping<AccountId32>>::into_account_id(
handle.context().caller,
);

// Transfer the amount back to the caller before executing the staking operation
// let caller = handle.context().caller;
let amount = handle.context().apparent_value;

if !amount.is_zero() {
transfer_back_to_caller(smart_contract_address, &account_id, amount)?;
}

let result = call.dispatch(RawOrigin::Signed(account_id.clone()).into());
match &result {
Ok(post_info) => log::info!("Dispatch succeeded. Post info: {:?}", post_info),
Err(dispatch_error) => log::error!("Dispatch failed. Error: {:?}", dispatch_error),
}
match result {
Ok(_) => Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: vec![],
}),
Err(_) => Err(PrecompileFailure::Error {
exit_status: ExitError::Other("Subtensor call failed".into()),
}),
}
}

pub fn get_pubkey(data: &[u8]) -> Result<(AccountId32, vec::Vec<u8>), PrecompileFailure> {
let mut pubkey = [0u8; 32];
pubkey.copy_from_slice(get_slice(data, 0, 32)?);

Ok((
pubkey.into(),
data.get(4..)
.map_or_else(vec::Vec::new, |slice| slice.to_vec()),
))
}
58 changes: 58 additions & 0 deletions runtime/src/precompiles/neuron.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use pallet_evm::{ExitError, PrecompileFailure, PrecompileHandle, PrecompileResult};

use crate::precompiles::{dispatch, get_method_id, get_pubkey, get_slice};
use sp_runtime::AccountId32;
use sp_std::vec;

use crate::{Runtime, RuntimeCall};
pub const NEURON_PRECOMPILE_INDEX: u64 = 2052;

// this is neuron smart contract's(0x0000000000000000000000000000000000000804) sr25519 address
pub const NEURON_CONTRACT_ADDRESS: &str = "5GKZiUUgTnWSz3BgiVBMehEKkLszsG4ZXnvgWpWFUFKqrqyn";

pub struct NeuronPrecompile;

impl NeuronPrecompile {
pub fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
let txdata = handle.input();
let method_id = get_slice(txdata, 0, 4)?;
let method_input = txdata
.get(4..)
.map_or_else(vec::Vec::new, |slice| slice.to_vec()); // Avoiding borrowing conflicts

match method_id {
id if id == get_method_id("burnedRegister(uint16,bytes32)") => {
Self::burned_register(handle, &method_input)
}

_ => Err(PrecompileFailure::Error {
exit_status: ExitError::InvalidRange,
}),
}
}

pub fn burned_register(handle: &mut impl PrecompileHandle, data: &[u8]) -> PrecompileResult {
let (netuid, hotkey) = Self::parse_netuid_hotkey_parameter(data)?;
let call =
RuntimeCall::SubtensorModule(pallet_subtensor::Call::<Runtime>::burned_register {
netuid,
hotkey,
});
dispatch(handle, call, NEURON_CONTRACT_ADDRESS)
}

fn parse_netuid_hotkey_parameter(data: &[u8]) -> Result<(u16, AccountId32), PrecompileFailure> {
if data.len() < 64 {
return Err(PrecompileFailure::Error {
exit_status: ExitError::InvalidRange,
});
}
let mut netuid_vec = [0u8; 2];
netuid_vec.copy_from_slice(get_slice(data, 30, 32)?);
let netuid = u16::from_be_bytes(netuid_vec);

let (hotkey, _) = get_pubkey(get_slice(data, 32, 64)?)?;

Ok((netuid, hotkey))
}
}
20 changes: 20 additions & 0 deletions runtime/src/precompiles/solidity/neuron.abi
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[
{
"inputs": [
{
"internalType": "uint16",
"name": "netuid",
"type": "uint16"
},
{
"internalType": "bytes32",
"name": "hotkey",
"type": "bytes32"
}
],
"name": "burnedRegister",
"outputs": [],
"stateMutability": "payable",
"type": "function"
}
]
14 changes: 14 additions & 0 deletions runtime/src/precompiles/solidity/neuron.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pragma solidity ^0.8.0;

address constant INeuron_ADDRESS = 0x0000000000000000000000000000000000000804;

interface INeuron {
/**
* @dev Registers a neuron by calling `do_burned_registration` internally with the origin set to the ss58 mirror of the H160 address.
* This allows the H160 to further call neuron-related methods and receive emissions.
*
* @param netuid The subnet to register the neuron to (uint16).
* @param hotkey The hotkey public key (32 bytes).
*/
function burnedRegister(uint16 netuid, bytes32 hotkey) external payable;
}
Loading
Loading