Skip to content

Commit b717be5

Browse files
committed
Merge branch 'feat/reject-incorrect-sudo-exts' of github.com:opentensor/subtensor into feat/reject-incorrect-sudo-exts
2 parents 84e2ba6 + e16a92e commit b717be5

File tree

12 files changed

+197
-54
lines changed

12 files changed

+197
-54
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

chain-extensions/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pallet-subtensor-proxy.workspace = true
3434
pallet-drand.workspace = true
3535
subtensor-swap-interface.workspace = true
3636
num_enum.workspace = true
37+
substrate-fixed.workspace = true
3738

3839
[lints]
3940
workspace = true
@@ -64,4 +65,5 @@ std = [
6465
"pallet-drand/std",
6566
"subtensor-swap-interface/std",
6667
"num_enum/std",
68+
"substrate-fixed/std",
6769
]

chain-extensions/src/lib.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ use pallet_subtensor_proxy as pallet_proxy;
1818
use pallet_subtensor_proxy::WeightInfo;
1919
use sp_runtime::{DispatchError, Weight, traits::StaticLookup};
2020
use sp_std::marker::PhantomData;
21+
use substrate_fixed::types::U96F32;
2122
use subtensor_runtime_common::{AlphaCurrency, NetUid, ProxyType, TaoCurrency};
23+
use subtensor_swap_interface::SwapHandler;
2224

2325
#[derive(DebugNoBound)]
2426
pub struct SubtensorChainExtension<T>(PhantomData<T>);
@@ -33,7 +35,8 @@ impl<T> ChainExtension<T> for SubtensorChainExtension<T>
3335
where
3436
T: pallet_subtensor::Config
3537
+ pallet_contracts::Config
36-
+ pallet_proxy::Config<ProxyType = ProxyType>,
38+
+ pallet_proxy::Config<ProxyType = ProxyType>
39+
+ pallet_subtensor_swap::Config,
3740
T::AccountId: Clone,
3841
<<T as SysConfig>::Lookup as StaticLookup>::Source: From<<T as SysConfig>::AccountId>,
3942
{
@@ -54,7 +57,8 @@ impl<T> SubtensorChainExtension<T>
5457
where
5558
T: pallet_subtensor::Config
5659
+ pallet_contracts::Config
57-
+ pallet_proxy::Config<ProxyType = ProxyType>,
60+
+ pallet_proxy::Config<ProxyType = ProxyType>
61+
+ pallet_subtensor_swap::Config,
5862
T::AccountId: Clone,
5963
{
6064
fn dispatch<Env>(env: &mut Env) -> Result<RetVal, DispatchError>
@@ -506,6 +510,26 @@ where
506510
}
507511
}
508512
}
513+
FunctionId::GetAlphaPriceV1 => {
514+
let netuid: NetUid = env
515+
.read_as()
516+
.map_err(|_| DispatchError::Other("Failed to decode input parameters"))?;
517+
518+
let current_alpha_price =
519+
<pallet_subtensor_swap::Pallet<T> as SwapHandler>::current_alpha_price(
520+
netuid.into(),
521+
);
522+
523+
let price = current_alpha_price.saturating_mul(U96F32::from_num(1_000_000_000));
524+
let price: u64 = price.saturating_to_num();
525+
526+
let encoded_result = price.encode();
527+
528+
env.write_output(&encoded_result)
529+
.map_err(|_| DispatchError::Other("Failed to write output"))?;
530+
531+
Ok(RetVal::Converging(Output::Success as u32))
532+
}
509533
}
510534
}
511535
}

chain-extensions/src/tests.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
use super::{SubtensorChainExtension, SubtensorExtensionEnv, mock};
44
use crate::types::{FunctionId, Output};
5-
use codec::Encode;
5+
use codec::{Decode, Encode};
66
use frame_support::{assert_ok, weights::Weight};
77
use frame_system::RawOrigin;
88
use pallet_contracts::chain_extension::RetVal;
99
use pallet_subtensor::DefaultMinStake;
1010
use sp_core::Get;
1111
use sp_core::U256;
1212
use sp_runtime::DispatchError;
13+
use substrate_fixed::types::U96F32;
1314
use subtensor_runtime_common::{AlphaCurrency, Currency as CurrencyTrait, NetUid, TaoCurrency};
1415
use subtensor_swap_interface::SwapHandler;
1516

@@ -964,3 +965,41 @@ fn unstake_all_success_unstakes_balance() {
964965
assert!(post_balance > pre_balance);
965966
});
966967
}
968+
969+
#[test]
970+
fn get_alpha_price_returns_encoded_price() {
971+
mock::new_test_ext(1).execute_with(|| {
972+
let owner_hotkey = U256::from(8001);
973+
let owner_coldkey = U256::from(8002);
974+
let caller = U256::from(8003);
975+
976+
let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey);
977+
978+
// Set up reserves to establish a price
979+
let tao_reserve = TaoCurrency::from(150_000_000_000u64);
980+
let alpha_reserve = AlphaCurrency::from(100_000_000_000u64);
981+
mock::setup_reserves(netuid, tao_reserve, alpha_reserve);
982+
983+
// Get expected price from swap handler
984+
let expected_price =
985+
<pallet_subtensor_swap::Pallet<mock::Test> as SwapHandler>::current_alpha_price(
986+
netuid.into(),
987+
);
988+
let expected_price_scaled = expected_price.saturating_mul(U96F32::from_num(1_000_000_000));
989+
let expected_price_u64: u64 = expected_price_scaled.saturating_to_num();
990+
991+
let mut env = MockEnv::new(FunctionId::GetAlphaPriceV1, caller, netuid.encode());
992+
993+
let ret = SubtensorChainExtension::<mock::Test>::dispatch(&mut env).unwrap();
994+
assert_success(ret);
995+
assert!(env.charged_weight().is_none());
996+
997+
// Decode the output
998+
let output_price: u64 = Decode::decode(&mut &env.output()[..]).unwrap();
999+
1000+
assert_eq!(
1001+
output_price, expected_price_u64,
1002+
"Price should match expected value"
1003+
);
1004+
});
1005+
}

chain-extensions/src/types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub enum FunctionId {
2020
SetColdkeyAutoStakeHotkeyV1 = 12,
2121
AddProxyV1 = 13,
2222
RemoveProxyV1 = 14,
23+
GetAlphaPriceV1 = 15,
2324
}
2425

2526
#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, Debug)]

contract-tests/bittensor/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub enum FunctionId {
2222
SetColdkeyAutoStakeHotkeyV1 = 12,
2323
AddProxyV1 = 13,
2424
RemoveProxyV1 = 14,
25+
GetAlphaPriceV1 = 15,
2526
}
2627

2728
#[ink::chain_extension(extension = 0x1000)]
@@ -127,6 +128,9 @@ pub trait RuntimeReadWrite {
127128

128129
#[ink(function = 14)]
129130
fn remove_proxy(delegate: <CustomEnvironment as ink::env::Environment>::AccountId);
131+
132+
#[ink(function = 15)]
133+
fn get_alpha_price(netuid: NetUid) -> u64;
130134
}
131135

132136
#[ink::scale_derive(Encode, Decode, TypeInfo)]
@@ -412,5 +416,13 @@ mod bittensor {
412416
.remove_proxy(delegate.into())
413417
.map_err(|_e| ReadWriteErrorCode::WriteFailed)
414418
}
419+
420+
#[ink(message)]
421+
pub fn get_alpha_price(&self, netuid: u16) -> Result<u64, ReadWriteErrorCode> {
422+
self.env()
423+
.extension()
424+
.get_alpha_price(netuid.into())
425+
.map_err(|_e| ReadWriteErrorCode::ReadFailed)
426+
}
415427
}
416428
}

contract-tests/run-ci.sh

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
echo "start run-ci.sh"
44

5+
cd contract-tests
6+
7+
cd bittensor
8+
9+
rustup component add rust-src
10+
cargo install cargo-contract
11+
cargo contract build --release
12+
13+
cd ../..
14+
515
scripts/localnet.sh &>/dev/null &
616

717
i=1
@@ -15,7 +25,7 @@ while [ $i -le 2000 ]; do
1525
done
1626

1727
# port not available exit with error
18-
if [ "$i" -eq 1000 ]; then
28+
if [ "$i" -eq 2000 ]; then
1929
exit 1
2030
fi
2131

@@ -28,14 +38,6 @@ fi
2838

2939
cd contract-tests
3040

31-
cd bittensor
32-
33-
rustup component add rust-src
34-
cargo install cargo-contract
35-
cargo contract build --release
36-
37-
cd ..
38-
3941
# required for papi in get-metadata.sh, but we cannot run yarn before papi as it adds the descriptors to the package.json which won't resolve
4042
npm i -g polkadot-api
4143

contract-tests/test/wasm.contract.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,4 +563,25 @@ describe("Test wasm contract", () => {
563563
assert.ok(proxiesAfterRemove !== undefined)
564564
assert.ok(proxiesAfterRemove[0].length === 0)
565565
})
566+
567+
it("Can get alpha price", async () => {
568+
const message = inkClient.message("get_alpha_price")
569+
const data = message.encode({
570+
netuid: netuid,
571+
})
572+
573+
const response = await api.apis.ContractsApi.call(
574+
convertPublicKeyToSs58(hotkey.publicKey),
575+
contractAddress,
576+
BigInt(0),
577+
undefined,
578+
undefined,
579+
Binary.fromBytes(data.asBytes()),
580+
)
581+
582+
assert.ok(response.result.success)
583+
const result = message.decode(response.result.value).value.value
584+
585+
assert.ok(result !== undefined)
586+
})
566587
});

node/src/mev_shield/author.rs

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,8 @@ pub async fn submit_announce_extrinsic<B, C, Pool>(
316316
) -> anyhow::Result<()>
317317
where
318318
B: sp_runtime::traits::Block,
319-
C: sc_client_api::HeaderBackend<B> + Send + Sync + 'static,
319+
C: sc_client_api::HeaderBackend<B> + sp_api::ProvideRuntimeApi<B> + Send + Sync + 'static,
320+
C::Api: sp_api::Core<B>,
320321
Pool: sc_transaction_pool_api::TransactionPool<Block = B> + Send + Sync + 'static,
321322
B::Extrinsic: From<sp_runtime::OpaqueExtrinsic>,
322323
B::Hash: AsRef<[u8]>,
@@ -325,12 +326,13 @@ where
325326
use runtime::{RuntimeCall, SignedPayload, UncheckedExtrinsic};
326327

327328
use sc_transaction_pool_api::TransactionSource;
329+
use sp_api::Core as _;
328330
use sp_core::H256;
329331
use sp_runtime::codec::Encode;
330332
use sp_runtime::{
331333
BoundedVec, MultiSignature,
332334
generic::Era,
333-
traits::{ConstU32, TransactionExtension},
335+
traits::{ConstU32, SaturatedConversion, TransactionExtension},
334336
};
335337

336338
fn to_h256<H: AsRef<[u8]>>(h: H) -> H256 {
@@ -365,13 +367,23 @@ where
365367

366368
// 2) Build the transaction extensions exactly like the runtime.
367369
type Extra = runtime::TransactionExtensions;
370+
371+
let info = client.info();
372+
let at_hash = info.best_hash;
373+
let at_hash_h256: H256 = to_h256(at_hash);
374+
let genesis_h256: H256 = to_h256(info.genesis_hash);
375+
376+
const ERA_PERIOD: u64 = 12;
377+
let current_block: u64 = info.best_number.saturated_into();
378+
let era = Era::mortal(ERA_PERIOD, current_block);
379+
368380
let extra: Extra =
369381
(
370382
frame_system::CheckNonZeroSender::<runtime::Runtime>::new(),
371383
frame_system::CheckSpecVersion::<runtime::Runtime>::new(),
372384
frame_system::CheckTxVersion::<runtime::Runtime>::new(),
373385
frame_system::CheckGenesis::<runtime::Runtime>::new(),
374-
frame_system::CheckEra::<runtime::Runtime>::from(Era::Immortal),
386+
frame_system::CheckEra::<runtime::Runtime>::from(era),
375387
node_subtensor_runtime::check_nonce::CheckNonce::<runtime::Runtime>::from(nonce).into(),
376388
frame_system::CheckWeight::<runtime::Runtime>::new(),
377389
node_subtensor_runtime::sudo_wrapper::SudoTransactionExtension::<runtime::Runtime>::new(
@@ -391,22 +403,36 @@ where
391403
// 3) Manually construct the `Implicit` tuple that the runtime will also derive.
392404
type Implicit = <Extra as TransactionExtension<RuntimeCall>>::Implicit;
393405

394-
let info = client.info();
395-
let genesis_h256: H256 = to_h256(info.genesis_hash);
406+
// Try to get the *current* runtime version from on-chain WASM; if that fails,
407+
// fall back to the compiled runtime::VERSION.
408+
let (spec_version, tx_version) = match client.runtime_api().version(at_hash) {
409+
Ok(v) => (v.spec_version, v.transaction_version),
410+
Err(e) => {
411+
log::debug!(
412+
target: "mev-shield",
413+
"runtime_api::version failed at_hash={at_hash:?}: {e:?}; \
414+
falling back to compiled runtime::VERSION",
415+
);
416+
(
417+
runtime::VERSION.spec_version,
418+
runtime::VERSION.transaction_version,
419+
)
420+
}
421+
};
396422

397423
let implicit: Implicit = (
398-
(), // CheckNonZeroSender
399-
runtime::VERSION.spec_version, // CheckSpecVersion::Implicit = u32
400-
runtime::VERSION.transaction_version, // CheckTxVersion::Implicit = u32
401-
genesis_h256, // CheckGenesis::Implicit = Hash
402-
genesis_h256, // CheckEra::Implicit (Immortal => genesis hash)
403-
(), // CheckNonce::Implicit = ()
404-
(), // CheckWeight::Implicit = ()
405-
(), // SudoTransactionExtension::Implicit = ()
406-
(), // ChargeTransactionPaymentWrapper::Implicit = ()
407-
(), // SubtensorTransactionExtension::Implicit = ()
408-
(), // DrandPriority::Implicit = ()
409-
None, // CheckMetadataHash::Implicit = Option<[u8; 32]>
424+
(), // CheckNonZeroSender
425+
spec_version, // dynamic or fallback spec_version
426+
tx_version, // dynamic or fallback transaction_version
427+
genesis_h256, // CheckGenesis::Implicit = Hash
428+
at_hash_h256, // CheckEra::Implicit = hash of the block the tx is created at
429+
(), // CheckNonce::Implicit = ()
430+
(), // CheckWeight::Implicit = ()
431+
(), // SudoTransactionExtension::Implicit = ()
432+
(), // ChargeTransactionPaymentWrapper::Implicit = ()
433+
(), // SubtensorTransactionExtension::Implicit = ()
434+
(), // DrandPriority::Implicit = ()
435+
None, // CheckMetadataHash::Implicit = Option<[u8; 32]>
410436
);
411437

412438
// 4) Build the exact signable payload from call + extra + implicit.
@@ -436,12 +462,13 @@ where
436462
let opaque: sp_runtime::OpaqueExtrinsic = uxt.into();
437463
let xt: <B as sp_runtime::traits::Block>::Extrinsic = opaque.into();
438464

439-
pool.submit_one(info.best_hash, TransactionSource::Local, xt)
465+
pool.submit_one(at_hash, TransactionSource::Local, xt)
440466
.await?;
441467

442468
log::debug!(
443469
target: "mev-shield",
444-
"announce_next_key submitted: xt=0x{xt_hash_hex}, nonce={nonce:?}",
470+
"announce_next_key submitted: xt=0x{xt_hash_hex}, nonce={nonce:?}, \
471+
spec_version={spec_version}, tx_version={tx_version}, era={era:?}",
445472
);
446473

447474
Ok(())

0 commit comments

Comments
 (0)