-
Notifications
You must be signed in to change notification settings - Fork 322
Mz/feat/staking precompile #777
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
gztensor
merged 6 commits into
RaoFoundation:feat/evm-devnet-ready
from
soring323:mz/feat/staking-precompile
Sep 5, 2024
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
8285c39
add add staking logic
soring323 609005b
refactor execute function
soring323 231e586
add remove staking function
soring323 074c9b6
fix lint issue
soring323 fad6d17
add refund logic to caller
soring323 7e047fa
fix lint issue
soring323 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,25 +1,139 @@ | ||
| /// Staking precompile's goal is to allow interaction between EVM users and smart contracts and | ||
| /// subtensor staking functionality, namely add_stake, and remove_stake extrinsics. | ||
| /// | ||
| /// Additional requirement is to preserve compatibility with Ethereum indexers, which requires | ||
| /// no balance transfers from EVM accounts without a corresponding transaction that can be | ||
| /// parsed by an indexer. | ||
| /// | ||
| /// Implementation of add_stake: | ||
| /// - User transfers balance that will be staked to the precompile address with a payable | ||
| /// method addStake. This method also takes hotkey public key (bytes32) of the hotkey | ||
| /// that the stake should be assigned to. | ||
| /// - Precompile transfers the balance back to the signing address, and then invokes | ||
| /// do_add_stake from subtensor pallet with signing origin that mmatches to HashedAddressMapping | ||
| /// of the message sender, which will effectively withdraw and stake balance from the message | ||
| /// sender. | ||
| /// - Precompile checks the result of do_add_stake and, in case of a failure, reverts the transaction, | ||
| /// and leaves the balance on the message sender account. | ||
| /// | ||
| /// Implementation of remove_stake: | ||
| /// - User involkes removeStake method and specifies hotkey public key (bytes32) of the hotkey | ||
| /// to remove stake from, and the amount to unstake. | ||
| /// - Precompile calls do_remove_stake method of the subtensor pallet with the signing origin of message | ||
| /// sender, which effectively unstakes the specified amount and credits it to the message sender | ||
| /// - Precompile checks the result of do_remove_stake and, in case of a failure, reverts the transaction. | ||
| /// | ||
| use frame_system::RawOrigin; | ||
| use pallet_evm::{AddressMapping, HashedAddressMapping}; | ||
| use pallet_evm::{ | ||
| ExitError, ExitSucceed, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult, | ||
| }; | ||
| use sp_core::crypto::Ss58Codec; | ||
| use sp_core::U256; | ||
| use sp_runtime::traits::BlakeTwo256; | ||
| use sp_runtime::traits::Dispatchable; | ||
| use sp_runtime::AccountId32; | ||
|
|
||
| use crate::precompiles::{get_method_id, get_slice}; | ||
| use sp_std::vec; | ||
|
|
||
| use crate::{Runtime, RuntimeCall}; | ||
| pub const STAKING_PRECOMPILE_INDEX: u64 = 2049; | ||
|
|
||
| pub struct StakingPrecompile; | ||
|
|
||
| impl StakingPrecompile { | ||
| pub fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult { | ||
| let txdata = handle.input(); | ||
| let method_id = get_slice(txdata, 0, 4)?; | ||
| let method_input = txdata[4..].to_vec(); // Avoiding borrowing conflicts | ||
|
|
||
| match method_id { | ||
| id if id == get_method_id("addStake(bytes32)") => { | ||
| Self::add_stake(handle, &method_input) | ||
| } | ||
| id if id == get_method_id("removeStake(bytes32,uint64)") => { | ||
| Self::remove_stake(handle, &method_input) | ||
| } | ||
| _ => Err(PrecompileFailure::Error { | ||
| exit_status: ExitError::InvalidRange, | ||
| }), | ||
| } | ||
| } | ||
|
|
||
| fn add_stake(handle: &mut impl PrecompileHandle, data: &[u8]) -> PrecompileResult { | ||
| let hotkey = Self::parse_hotkey(data)?.into(); | ||
| let amount: U256 = handle.context().apparent_value; | ||
| // Create the add_stake call | ||
| let call = RuntimeCall::SubtensorModule(pallet_subtensor::Call::<Runtime>::add_stake { | ||
| hotkey, | ||
| amount_staked: amount.as_u64(), | ||
| }); | ||
| // Dispatch the add_stake call | ||
| Self::dispatch(handle, call) | ||
| } | ||
| fn remove_stake(handle: &mut impl PrecompileHandle, data: &[u8]) -> PrecompileResult { | ||
| let hotkey = Self::parse_hotkey(data)?.into(); | ||
| let amount = U256::from_big_endian(&data[32..40]).as_u64(); // Assuming the next 8 bytes represent the amount | ||
| let call = RuntimeCall::SubtensorModule(pallet_subtensor::Call::<Runtime>::remove_stake { | ||
| hotkey, | ||
| amount_unstaked: amount, | ||
| }); | ||
| Self::dispatch(handle, call) | ||
| } | ||
|
|
||
| fn parse_hotkey(data: &[u8]) -> Result<[u8; 32], PrecompileFailure> { | ||
| if data.len() < 32 { | ||
| return Err(PrecompileFailure::Error { | ||
| exit_status: ExitError::InvalidRange, | ||
| }); | ||
| } | ||
| let mut hotkey = [0u8; 32]; | ||
| hotkey.copy_from_slice(&data[0..32]); | ||
| Ok(hotkey) | ||
| } | ||
|
|
||
| fn dispatch(handle: &mut impl PrecompileHandle, call: RuntimeCall) -> 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() { | ||
| Self::transfer_back_to_caller(&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()), | ||
| }), | ||
| } | ||
| } | ||
|
|
||
| fn transfer_back_to_caller( | ||
| account_id: &AccountId32, | ||
| amount: U256, | ||
| ) -> Result<(), PrecompileFailure> { | ||
| // this is staking smart contract's(0x0000000000000000000000000000000000000800) sr25519 address | ||
| let smart_contract_account_id = | ||
| match AccountId32::from_ss58check("5CwnBK9Ack1mhznmCnwiibCNQc174pYQVktYW3ayRpLm4K2X") { | ||
| Ok(addr) => addr, | ||
| Err(_) => { | ||
| return Err(PrecompileFailure::Error { | ||
| exit_status: ExitError::Other("Invalid SS58 address".into()), | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| // 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.as_u64(), | ||
| }); | ||
|
|
||
| // 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(()) | ||
| } | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.