Skip to content
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion contract-tests/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"scripts": {
"test": "mocha --timeout 999999 --retries 3 --file src/setup.ts --require ts-node/register test/*test.ts"
"test": "TS_NODE_PREFER_TS_EXTS=1 TS_NODE_TRANSPILE_ONLY=1 mocha --timeout 999999 --retries 3 --file src/setup.ts --require ts-node/register --extension ts \"test/**/*.ts\""
},
"keywords": [],
"author": "",
Expand Down
73 changes: 71 additions & 2 deletions contract-tests/test/neuron.precompile.reveal-weights.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as assert from "assert";
import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate"
import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair, waitForTransactionWithRetry } from "../src/substrate"
import { devnet } from "@polkadot-api/descriptors"
import { PolkadotSigner, TypedApi } from "polkadot-api";
import { convertPublicKeyToSs58, convertH160ToSS58 } from "../src/address-utils"
Expand All @@ -23,6 +23,16 @@ const values = [5];
const salt = [9];
const version_key = 0;

async function setStakeThreshold(
api: TypedApi<typeof devnet>,
alice: PolkadotSigner,
minStake: bigint,
) {
const internalCall = api.tx.AdminUtils.sudo_set_stake_threshold({ min_stake: minStake })
const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall })
await waitForTransactionWithRetry(api, tx, alice)
}

function getCommitHash(netuid: number, address: string) {
const registry = new TypeRegistry();
let publicKey = convertH160ToPublicKey(address);
Expand Down Expand Up @@ -53,7 +63,7 @@ describe("Test neuron precompile reveal weights", () => {
const coldkey = getRandomSubstrateKeypair();

let api: TypedApi<typeof devnet>
let commitEpoch: number;
let commitEpoch: number | undefined;

// sudo account alice as signer
let alice: PolkadotSigner;
Expand Down Expand Up @@ -86,11 +96,52 @@ describe("Test neuron precompile reveal weights", () => {
assert.equal(uid, uids[0])
})

async function ensureCommitEpoch(netuid: number, contract: ethers.Contract) {
if (commitEpoch !== undefined) {
return
}

const ss58Address = convertH160ToSS58(wallet.address)
const existingCommits = await api.query.SubtensorModule.WeightCommits.getValue(
netuid,
ss58Address
)
if (Array.isArray(existingCommits) && existingCommits.length > 0) {
const entry = existingCommits[0]
const commitBlockRaw =
Array.isArray(entry) && entry.length > 1 ? entry[1] : undefined
const commitBlock =
typeof commitBlockRaw === "bigint"
? Number(commitBlockRaw)
: Number(commitBlockRaw ?? NaN)
if (Number.isFinite(commitBlock)) {
commitEpoch = Math.trunc(commitBlock / (100 + 1))
return
}
}

await setStakeThreshold(api, alice, BigInt(0))
const commitHash = getCommitHash(netuid, wallet.address)
const tx = await contract.commitWeights(netuid, commitHash)
await tx.wait()

const commitBlock = await api.query.System.Number.getValue()
commitEpoch = Math.trunc(commitBlock / (100 + 1))
}

it("EVM neuron commit weights via call precompile", async () => {
let totalNetworks = await api.query.SubtensorModule.TotalNetworks.getValue()
const subnetId = totalNetworks - 1
const commitHash = getCommitHash(subnetId, wallet.address)
const contract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet);

await setStakeThreshold(api, alice, BigInt(1))
await assert.rejects(async () => {
const tx = await contract.commitWeights(subnetId, commitHash)
await tx.wait()
})
await setStakeThreshold(api, alice, BigInt(0))

try {
const tx = await contract.commitWeights(subnetId, commitHash)
await tx.wait()
Expand Down Expand Up @@ -120,6 +171,11 @@ describe("Test neuron precompile reveal weights", () => {
// set interval epoch as 1, it is the minimum value now
await setCommitRevealWeightsInterval(api, netuid, BigInt(1))

await ensureCommitEpoch(netuid, contract)
if (commitEpoch === undefined) {
throw new Error("commitEpoch should be set before revealing weights")
}

while (true) {
const currentBlock = await api.query.System.Number.getValue()
const currentEpoch = Math.trunc(currentBlock / (100 + 1))
Expand All @@ -130,6 +186,19 @@ describe("Test neuron precompile reveal weights", () => {
await new Promise(resolve => setTimeout(resolve, 1000))
}

await setStakeThreshold(api, alice, BigInt(1))
await assert.rejects(async () => {
const tx = await contract.revealWeights(
netuid,
uids,
values,
salt,
version_key
);
await tx.wait()
})
await setStakeThreshold(api, alice, BigInt(0))

const tx = await contract.revealWeights(
netuid,
uids,
Expand Down
39 changes: 36 additions & 3 deletions contract-tests/test/subnet.precompile.hyperparameter.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as assert from "assert";

import { getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate"
import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair, waitForTransactionWithRetry } from "../src/substrate"
import { devnet } from "@polkadot-api/descriptors"
import { TypedApi } from "polkadot-api";
import { convertPublicKeyToSs58 } from "../src/address-utils"
import { Binary, TypedApi, getTypedCodecs } from "polkadot-api";
import { convertH160ToSS58, convertPublicKeyToSs58 } from "../src/address-utils"
import { generateRandomEthersWallet } from "../src/utils";
import { ISubnetABI, ISUBNET_ADDRESS } from "../src/contracts/subnet"
import { ethers } from "ethers"
Expand Down Expand Up @@ -545,4 +545,37 @@ describe("Test the Subnet precompile contract", () => {
assert.equal(valueFromContract, newValue)
assert.equal(valueFromContract, onchainValue);
})

it("Rejects subnet precompile calls when coldkey swap is scheduled (tx extension)", async () => {
const totalNetwork = await api.query.SubtensorModule.TotalNetworks.getValue()
const contract = new ethers.Contract(ISUBNET_ADDRESS, ISubnetABI, wallet);
const netuid = totalNetwork - 1;

const coldkeySs58 = convertH160ToSS58(wallet.address)
const newColdkeySs58 = convertPublicKeyToSs58(hotkey1.publicKey)
const currentBlock = await api.query.System.Number.getValue()
const executionBlock = currentBlock + 10

const codec = await getTypedCodecs(devnet);
const valueBytes = codec.query.SubtensorModule.ColdkeySwapScheduled.value.enc([
executionBlock,
newColdkeySs58,
])
const key = await api.query.SubtensorModule.ColdkeySwapScheduled.getKey(coldkeySs58);

// Use sudo + set_storage since the swap-scheduled check only exists in the tx extension.
const setStorageCall = api.tx.System.set_storage({
items: [[Binary.fromHex(key), Binary.fromBytes(valueBytes)]],
})
const sudoTx = api.tx.Sudo.sudo({ call: setStorageCall.decodedCall })
await waitForTransactionWithRetry(api, sudoTx, getAliceSigner())

const storedValue = await api.query.SubtensorModule.ColdkeySwapScheduled.getValue(coldkeySs58)
assert.deepStrictEqual(storedValue, [executionBlock, newColdkeySs58])

await assert.rejects(async () => {
const tx = await contract.setServingRateLimit(netuid, 100);
await tx.wait();
})
})
})
10 changes: 7 additions & 3 deletions precompiles/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ repository = "https://github.com/opentensor/subtensor/"
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
# codec.workspace = true
ed25519-dalek = { workspace = true, features = ["alloc"] }
fp-evm.workspace = true
frame-support.workspace = true
Expand All @@ -25,6 +26,7 @@ pallet-evm-precompile-simple.workspace = true
pallet-evm-precompile-bn128.workspace = true
pallet-subtensor-proxy.workspace = true
precompile-utils.workspace = true
scale-info.workspace = true
sp-core.workspace = true
sp-io.workspace = true
sp-runtime.workspace = true
Expand All @@ -43,24 +45,26 @@ workspace = true
[features]
default = ["std"]
std = [
# "codec/std",
"ed25519-dalek/std",
"fp-evm/std",
"frame-support/std",
"frame-system/std",
"log/std",
"pallet-admin-utils/std",
"pallet-balances/std",
"pallet-crowdloan/std",
"pallet-evm-precompile-bn128/std",
"pallet-evm-precompile-dispatch/std",
"pallet-evm-precompile-modexp/std",
"pallet-evm-precompile-sha3fips/std",
"pallet-evm-precompile-simple/std",
"pallet-evm-precompile-bn128/std",
"pallet-evm/std",
"pallet-crowdloan/std",
"pallet-subtensor-proxy/std",
"pallet-subtensor/std",
"pallet-subtensor-swap/std",
"pallet-subtensor/std",
"precompile-utils/std",
"scale-info/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
Expand Down
39 changes: 29 additions & 10 deletions precompiles/src/balance_transfer.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
use core::marker::PhantomData;

use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo};
use frame_support::dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo};
use frame_support::traits::IsSubType;
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 sp_runtime::traits::{AsSystemOriginSigner, Dispatchable, StaticLookup, UniqueSaturatedInto};

use crate::{PrecompileExt, PrecompileHandleExt};

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

impl<R> PrecompileExt<R::AccountId> for BalanceTransferPrecompile<R>
where
R: frame_system::Config + pallet_balances::Config + pallet_evm::Config,
R: frame_system::Config
+ pallet_balances::Config
+ pallet_evm::Config
+ pallet_subtensor::Config
+ Send
+ Sync
+ scale_info::TypeInfo,
R::AccountId: From<[u8; 32]>,
<R as frame_system::Config>::RuntimeCall:
GetDispatchInfo + Dispatchable<PostInfo = PostDispatchInfo>,
<R as frame_system::Config>::RuntimeOrigin: AsSystemOriginSigner<R::AccountId> + Clone,
<R as frame_system::Config>::RuntimeCall: GetDispatchInfo
+ Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+ IsSubType<pallet_balances::Call<R>>
+ IsSubType<pallet_subtensor::Call<R>>,
<R as frame_system::Config>::RuntimeCall: From<pallet_balances::Call<R>>
+ GetDispatchInfo
+ Dispatchable<PostInfo = PostDispatchInfo>,
+ Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
<<R as frame_system::Config>::Lookup as StaticLookup>::Source: From<R::AccountId>,
<R as pallet_balances::Config>::Balance: TryFrom<U256>,
{
Expand All @@ -29,13 +39,22 @@ where
#[precompile_utils::precompile]
impl<R> BalanceTransferPrecompile<R>
where
R: frame_system::Config + pallet_balances::Config + pallet_evm::Config,
R: frame_system::Config
+ pallet_balances::Config
+ pallet_evm::Config
+ pallet_subtensor::Config
+ Send
+ Sync
+ scale_info::TypeInfo,
R::AccountId: From<[u8; 32]>,
<R as frame_system::Config>::RuntimeCall:
GetDispatchInfo + Dispatchable<PostInfo = PostDispatchInfo>,
<R as frame_system::Config>::RuntimeOrigin: AsSystemOriginSigner<R::AccountId> + Clone,
<R as frame_system::Config>::RuntimeCall: GetDispatchInfo
+ Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+ IsSubType<pallet_balances::Call<R>>
+ IsSubType<pallet_subtensor::Call<R>>,
<R as frame_system::Config>::RuntimeCall: From<pallet_balances::Call<R>>
+ GetDispatchInfo
+ Dispatchable<PostInfo = PostDispatchInfo>,
+ Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
<<R as frame_system::Config>::Lookup as StaticLookup>::Source: From<R::AccountId>,
<R as pallet_balances::Config>::Balance: TryFrom<U256>,
{
Expand Down
37 changes: 30 additions & 7 deletions precompiles/src/crowdloan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,39 @@ use alloc::string::String;
use core::marker::PhantomData;

use fp_evm::{ExitError, PrecompileFailure};
use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo};
use frame_support::dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo};
use frame_support::traits::IsSubType;
use frame_system::RawOrigin;
use pallet_evm::AddressMapping;
use pallet_evm::PrecompileHandle;
use pallet_subtensor_proxy as pallet_proxy;
use precompile_utils::prelude::Address;
use precompile_utils::{EvmResult, solidity::Codec};
use sp_core::{ByteArray, H256};
use sp_runtime::traits::{Dispatchable, UniqueSaturatedInto};
use sp_runtime::traits::{AsSystemOriginSigner, Dispatchable, UniqueSaturatedInto};

use crate::{PrecompileExt, PrecompileHandleExt};

pub struct CrowdloanPrecompile<R>(PhantomData<R>);

impl<R> PrecompileExt<R::AccountId> for CrowdloanPrecompile<R>
where
R: frame_system::Config + pallet_evm::Config + pallet_crowdloan::Config + pallet_proxy::Config,
R: frame_system::Config
+ pallet_balances::Config
+ pallet_crowdloan::Config
+ pallet_evm::Config
+ pallet_proxy::Config
+ pallet_subtensor::Config
+ Send
+ Sync
+ scale_info::TypeInfo,
R::AccountId: From<[u8; 32]> + ByteArray,
<R as frame_system::Config>::RuntimeOrigin: AsSystemOriginSigner<R::AccountId> + Clone,
<R as frame_system::Config>::RuntimeCall: From<pallet_crowdloan::Call<R>>
+ GetDispatchInfo
+ Dispatchable<PostInfo = PostDispatchInfo>,
+ Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+ IsSubType<pallet_balances::Call<R>>
+ IsSubType<pallet_subtensor::Call<R>>,
<R as pallet_evm::Config>::AddressMapping: AddressMapping<R::AccountId>,
{
const INDEX: u64 = 2057;
Expand All @@ -31,14 +43,25 @@ where
#[precompile_utils::precompile]
impl<R> CrowdloanPrecompile<R>
where
R: frame_system::Config + pallet_evm::Config + pallet_crowdloan::Config + pallet_proxy::Config,
R: frame_system::Config
+ pallet_balances::Config
+ pallet_crowdloan::Config
+ pallet_evm::Config
+ pallet_proxy::Config
+ pallet_subtensor::Config
+ Send
+ Sync
+ scale_info::TypeInfo,
R::AccountId: From<[u8; 32]> + ByteArray,
<R as frame_system::Config>::RuntimeOrigin: AsSystemOriginSigner<R::AccountId> + Clone,
<R as frame_system::Config>::RuntimeCall: From<pallet_crowdloan::Call<R>>
+ GetDispatchInfo
+ Dispatchable<PostInfo = PostDispatchInfo>,
+ Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+ IsSubType<pallet_balances::Call<R>>
+ IsSubType<pallet_subtensor::Call<R>>,
<R as frame_system::Config>::RuntimeCall: From<pallet_crowdloan::Call<R>>
+ GetDispatchInfo
+ Dispatchable<PostInfo = PostDispatchInfo>,
+ Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
<R as pallet_evm::Config>::AddressMapping: AddressMapping<R::AccountId>,
{
#[precompile::public("getCrowdloan(uint32)")]
Expand Down
Loading
Loading