Skip to content

Commit 051b531

Browse files
authored
Merge pull request #1110 from opentensor/neuron-precompile
add subnets precompile
2 parents f1886e2 + cdfffaf commit 051b531

File tree

10 files changed

+540
-172
lines changed

10 files changed

+540
-172
lines changed

runtime/src/precompiles/balance_transfer.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
1-
use frame_system::RawOrigin;
21
use pallet_evm::{
32
BalanceConverter, ExitError, ExitSucceed, PrecompileHandle, PrecompileOutput, PrecompileResult,
43
};
54
use sp_runtime::traits::UniqueSaturatedInto;
65
use sp_std::vec;
76

87
use crate::precompiles::{
9-
bytes_to_account_id, get_method_id, get_slice, try_dispatch_runtime_call,
8+
contract_to_origin, get_method_id, get_pubkey, get_slice, try_dispatch_runtime_call,
109
};
1110
use crate::Runtime;
1211

1312
pub const BALANCE_TRANSFER_INDEX: u64 = 2048;
14-
15-
// This is a hardcoded hashed address mapping of 0x0000000000000000000000000000000000000800 to an
1613
// ss58 public key i.e., the contract sends funds it received to the destination address from the
1714
// method parameter.
1815
const CONTRACT_ADDRESS_SS58: [u8; 32] = [
@@ -51,15 +48,13 @@ impl BalanceTransferPrecompile {
5148
}
5249

5350
let address_bytes_dst = get_slice(txdata, 4, 36)?;
54-
let account_id_src = bytes_to_account_id(&CONTRACT_ADDRESS_SS58)?;
55-
let account_id_dst = bytes_to_account_id(address_bytes_dst)?;
51+
let (account_id_dst, _) = get_pubkey(address_bytes_dst)?;
5652

5753
let call = pallet_balances::Call::<Runtime>::transfer_allow_death {
5854
dest: account_id_dst.into(),
5955
value: amount_sub.unique_saturated_into(),
6056
};
61-
let origin = RawOrigin::Signed(account_id_src);
6257

63-
try_dispatch_runtime_call(handle, call, origin)
58+
try_dispatch_runtime_call(handle, call, contract_to_origin(&CONTRACT_ADDRESS_SS58)?)
6459
}
6560
}

runtime/src/precompiles/mod.rs

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use alloc::format;
44
use core::marker::PhantomData;
55

66
use frame_support::dispatch::{GetDispatchInfo, Pays};
7-
use frame_system::RawOrigin;
7+
88
use pallet_evm::{
99
ExitError, ExitSucceed, GasWeightMapping, IsPrecompileResult, Precompile, PrecompileFailure,
1010
PrecompileHandle, PrecompileOutput, PrecompileResult, PrecompileSet,
@@ -17,19 +17,25 @@ use sp_runtime::{traits::Dispatchable, AccountId32};
1717

1818
use crate::{Runtime, RuntimeCall};
1919

20+
use frame_system::RawOrigin;
21+
22+
use sp_std::vec;
23+
2024
// Include custom precompiles
2125
mod balance_transfer;
2226
mod ed25519;
2327
mod metagraph;
28+
mod neuron;
2429
mod staking;
30+
mod subnet;
2531

2632
use balance_transfer::*;
2733
use ed25519::*;
2834
use metagraph::*;
35+
use neuron::*;
2936
use staking::*;
30-
37+
use subnet::*;
3138
pub struct FrontierPrecompiles<R>(PhantomData<R>);
32-
3339
impl<R> Default for FrontierPrecompiles<R>
3440
where
3541
R: pallet_evm::Config,
@@ -46,7 +52,7 @@ where
4652
pub fn new() -> Self {
4753
Self(Default::default())
4854
}
49-
pub fn used_addresses() -> [H160; 11] {
55+
pub fn used_addresses() -> [H160; 13] {
5056
[
5157
hash(1),
5258
hash(2),
@@ -58,7 +64,9 @@ where
5864
hash(EDVERIFY_PRECOMPILE_INDEX),
5965
hash(BALANCE_TRANSFER_INDEX),
6066
hash(STAKING_PRECOMPILE_INDEX),
67+
hash(SUBNET_PRECOMPILE_INDEX),
6168
hash(METAGRAPH_PRECOMPILE_INDEX),
69+
hash(NEURON_PRECOMPILE_INDEX),
6270
]
6371
}
6472
}
@@ -83,9 +91,11 @@ where
8391
Some(BalanceTransferPrecompile::execute(handle))
8492
}
8593
a if a == hash(STAKING_PRECOMPILE_INDEX) => Some(StakingPrecompile::execute(handle)),
94+
a if a == hash(SUBNET_PRECOMPILE_INDEX) => Some(SubnetPrecompile::execute(handle)),
8695
a if a == hash(METAGRAPH_PRECOMPILE_INDEX) => {
8796
Some(MetagraphPrecompile::execute(handle))
8897
}
98+
a if a == hash(NEURON_PRECOMPILE_INDEX) => Some(NeuronPrecompile::execute(handle)),
8999

90100
_ => None,
91101
}
@@ -113,31 +123,55 @@ pub fn get_method_id(method_signature: &str) -> [u8; 4] {
113123
[hash[0], hash[1], hash[2], hash[3]]
114124
}
115125

116-
/// Convert bytes to AccountId32 with PrecompileFailure as Error
117-
/// which consumes all gas
118-
///
119-
pub fn bytes_to_account_id(account_id_bytes: &[u8]) -> Result<AccountId32, PrecompileFailure> {
120-
AccountId32::try_from(account_id_bytes).map_err(|_| {
121-
log::info!("Error parsing account id bytes {:?}", account_id_bytes);
122-
PrecompileFailure::Error {
123-
exit_status: ExitError::InvalidRange,
124-
}
125-
})
126-
}
127-
128126
/// Takes a slice from bytes with PrecompileFailure as Error
129127
///
130128
pub fn get_slice(data: &[u8], from: usize, to: usize) -> Result<&[u8], PrecompileFailure> {
131129
let maybe_slice = data.get(from..to);
132130
if let Some(slice) = maybe_slice {
133131
Ok(slice)
134132
} else {
133+
log::error!(
134+
"fail to get slice from data, {:?}, from {}, to {}",
135+
&data,
136+
from,
137+
to
138+
);
135139
Err(PrecompileFailure::Error {
136140
exit_status: ExitError::InvalidRange,
137141
})
138142
}
139143
}
140144

145+
pub fn get_pubkey(data: &[u8]) -> Result<(AccountId32, vec::Vec<u8>), PrecompileFailure> {
146+
let mut pubkey = [0u8; 32];
147+
pubkey.copy_from_slice(get_slice(data, 0, 32)?);
148+
149+
Ok((
150+
pubkey.into(),
151+
data.get(4..)
152+
.map_or_else(vec::Vec::new, |slice| slice.to_vec()),
153+
))
154+
}
155+
156+
fn parse_netuid(data: &[u8], offset: usize) -> Result<u16, PrecompileFailure> {
157+
if data.len() < offset + 2 {
158+
return Err(PrecompileFailure::Error {
159+
exit_status: ExitError::InvalidRange,
160+
});
161+
}
162+
163+
let mut netuid_bytes = [0u8; 2];
164+
netuid_bytes.copy_from_slice(get_slice(data, offset, offset + 2)?);
165+
let netuid: u16 = netuid_bytes[1] as u16 | ((netuid_bytes[0] as u16) << 8u16);
166+
167+
Ok(netuid)
168+
}
169+
170+
fn contract_to_origin(contract: &[u8; 32]) -> Result<RawOrigin<AccountId32>, PrecompileFailure> {
171+
let (account_id, _) = get_pubkey(contract)?;
172+
Ok(RawOrigin::Signed(account_id))
173+
}
174+
141175
/// Dispatches a runtime call, but also checks and records the gas costs.
142176
fn try_dispatch_runtime_call(
143177
handle: &mut impl PrecompileHandle,

runtime/src/precompiles/neuron.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use pallet_evm::{ExitError, PrecompileFailure, PrecompileHandle, PrecompileResult};
2+
3+
use crate::precompiles::{
4+
contract_to_origin, get_method_id, get_pubkey, get_slice, parse_netuid,
5+
try_dispatch_runtime_call,
6+
};
7+
use sp_runtime::AccountId32;
8+
use sp_std::vec;
9+
10+
use crate::{Runtime, RuntimeCall};
11+
pub const NEURON_PRECOMPILE_INDEX: u64 = 2052;
12+
13+
// ss58 public key i.e., the contract sends funds it received to the destination address from the
14+
// method parameter.
15+
const CONTRACT_ADDRESS_SS58: [u8; 32] = [
16+
0xbc, 0x46, 0x35, 0x79, 0xbc, 0x99, 0xf9, 0xee, 0x7c, 0x59, 0xed, 0xee, 0x20, 0x61, 0xa3, 0x09,
17+
0xd2, 0x1e, 0x68, 0xd5, 0x39, 0xb6, 0x40, 0xec, 0x66, 0x46, 0x90, 0x30, 0xab, 0x74, 0xc1, 0xdb,
18+
];
19+
pub struct NeuronPrecompile;
20+
21+
impl NeuronPrecompile {
22+
pub fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
23+
let txdata = handle.input();
24+
let method_id = get_slice(txdata, 0, 4)?;
25+
let method_input = txdata
26+
.get(4..)
27+
.map_or_else(vec::Vec::new, |slice| slice.to_vec()); // Avoiding borrowing conflicts
28+
29+
match method_id {
30+
id if id == get_method_id("burnedRegister(uint16,bytes32)") => {
31+
Self::burned_register(handle, &method_input)
32+
}
33+
34+
_ => Err(PrecompileFailure::Error {
35+
exit_status: ExitError::InvalidRange,
36+
}),
37+
}
38+
}
39+
40+
pub fn burned_register(handle: &mut impl PrecompileHandle, data: &[u8]) -> PrecompileResult {
41+
let (netuid, hotkey) = Self::parse_netuid_hotkey_parameter(data)?;
42+
let call =
43+
RuntimeCall::SubtensorModule(pallet_subtensor::Call::<Runtime>::burned_register {
44+
netuid,
45+
hotkey,
46+
});
47+
try_dispatch_runtime_call(handle, call, contract_to_origin(&CONTRACT_ADDRESS_SS58)?)
48+
}
49+
50+
fn parse_netuid_hotkey_parameter(data: &[u8]) -> Result<(u16, AccountId32), PrecompileFailure> {
51+
if data.len() < 64 {
52+
return Err(PrecompileFailure::Error {
53+
exit_status: ExitError::InvalidRange,
54+
});
55+
}
56+
let netuid = parse_netuid(data, 30)?;
57+
58+
let (hotkey, _) = get_pubkey(get_slice(data, 32, 64)?)?;
59+
60+
Ok((netuid, hotkey))
61+
}
62+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[
2+
{
3+
"inputs": [
4+
{
5+
"internalType": "uint16",
6+
"name": "netuid",
7+
"type": "uint16"
8+
},
9+
{
10+
"internalType": "bytes32",
11+
"name": "hotkey",
12+
"type": "bytes32"
13+
}
14+
],
15+
"name": "burnedRegister",
16+
"outputs": [],
17+
"stateMutability": "payable",
18+
"type": "function"
19+
}
20+
]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
pragma solidity ^0.8.0;
2+
3+
address constant INeuron_ADDRESS = 0x0000000000000000000000000000000000000804;
4+
5+
interface INeuron {
6+
/**
7+
* @dev Registers a neuron by calling `do_burned_registration` internally with the origin set to the ss58 mirror of the H160 address.
8+
* This allows the H160 to further call neuron-related methods and receive emissions.
9+
*
10+
* @param netuid The subnet to register the neuron to (uint16).
11+
* @param hotkey The hotkey public key (32 bytes).
12+
*/
13+
function burnedRegister(uint16 netuid, bytes32 hotkey) external payable;
14+
}

0 commit comments

Comments
 (0)