Skip to content
Closed
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();
})
})
})
4 changes: 1 addition & 3 deletions node/src/mev_shield/author.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,10 @@ impl Default for ShieldKeys {
}

/// Shared context state.
#[freeze_struct("62af7d26cf7c1271")]
#[freeze_struct("245b565abca7d403")]
#[derive(Clone)]
pub struct ShieldContext {
pub keys: Arc<Mutex<ShieldKeys>>,
pub timing: TimeParams,
}

/// Derive AEAD key directly from the 32‑byte ML‑KEM shared secret.
Expand Down Expand Up @@ -153,7 +152,6 @@ where
{
let ctx = ShieldContext {
keys: Arc::new(Mutex::new(ShieldKeys::new())),
timing: timing.clone(),
};

let aura_keys: Vec<sp_core::sr25519::Public> = keystore.sr25519_public_keys(AURA_KEY_TYPE);
Expand Down
Loading
Loading