Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 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
2 changes: 2 additions & 0 deletions .github/workflows/typescript-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ jobs:
binary: fast
- test: zombienet_subnets
binary: fast
- test: zombienet_evm
binary: fast

name: "typescript-e2e-${{ matrix.test }}"

Expand Down
7 changes: 1 addition & 6 deletions pallets/subtensor/src/staking/order_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,7 @@ impl<T: Config> OrderSwapInterface<T::AccountId> for Pallet<T> {
);
}
let alpha_out =
Self::stake_into_subnet(hotkey, coldkey, netuid, tao_amount, amm_limit, false, false)?;
if validate {
Self::set_stake_operation_limit(hotkey, coldkey, netuid);
}
Self::stake_into_subnet(hotkey, coldkey, netuid, tao_amount, amm_limit, false)?;
Ok(alpha_out)
}

Expand Down Expand Up @@ -136,7 +133,6 @@ impl<T: Config> OrderSwapInterface<T::AccountId> for Pallet<T> {
TaoBalance::from(tao_equiv) >= DefaultMinStake::<T>::get(),
Error::<T>::AmountTooLow
);
Self::ensure_stake_operation_limit_not_exceeded(from_hotkey, from_coldkey, netuid)?;
Self::ensure_available_to_unstake(from_coldkey, netuid, amount)?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Restore sender staking rate-limit validation

transfer_staked_alpha(..., validate_sender: true, ...) is the path pallets/limit-orders uses when collecting staked alpha from sell-order signers, and that call site explicitly relies on validate_sender to check the user's rate limit. The trait contract also says this flag must reject a sender that already staked in the current block. This hunk removes the only sender-side stake-operation-limit check and leaves only ensure_available_to_unstake, so a signer can stake and then transfer that same-block stake into order matching, bypassing the intended anti-same-block staking guard. Restore an equivalent rate-limit check before the availability check; the removed helper name may need to be reintroduced or replaced with the current rate-limit API.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Restore sender staking rate-limit validation

collect_assets in pallets/limit-orders calls this with validate_sender: true and documents that this path should check the seller's staking rate limit before pulling staked alpha into the pallet intermediary. The current implementation now validates hotkey existence, minimum amount, and locks, but no longer rejects a sender that is still inside the staking operation limit window before debiting stake. That lets repeated sell-side batch executions bypass the economic throttling that ordinary stake movement is supposed to enforce. Restore the sender-side staking rate-limit check before ensure_available_to_unstake.

}

Expand All @@ -145,7 +141,6 @@ impl<T: Config> OrderSwapInterface<T::AccountId> for Pallet<T> {
Self::hotkey_account_exists(to_hotkey),
Error::<T>::HotKeyAccountNotExists
);
Self::set_stake_operation_limit(to_hotkey, to_coldkey, netuid);
}

let available =
Expand Down
32 changes: 32 additions & 0 deletions ts-tests/moonwall.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,38 @@
"endpoints": ["ws://127.0.0.1:9947"]
}
]
}, {
"name": "zombienet_evm",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Wire the new EVM suite into CI

This registers a new zombienet_evm Moonwall suite, but .github/workflows/typescript-e2e.yml still only runs dev, zombienet_shield, zombienet_staking, zombienet_coldkey_swap, and zombienet_subnets. As written, the migrated EVM test can go stale because PR CI never invokes pnpm moonwall test zombienet_evm.

Add zombienet_evm to the run-e2e-tests matrix, likely against the release binary unless there is a specific fast-runtime reason.

"timeout": 600000,
"testFileDir": ["suites/zombienet_evm"],
"runScripts": [
"generate-types.sh",
"build-spec.sh"
],
"foundation": {
"type": "zombie",
"zombieSpec": {
"configPath": "./configs/zombie_node.json",
"skipBlockCheck": []
}
},
"vitestArgs": {
"bail": 1
},
"connections": [
{
"name": "Node",
"type": "papi",
"endpoints": ["ws://127.0.0.1:9947"],
"descriptor": "subtensor"
},
{
"name": "EVM",
"type": "ethers",
"endpoints": ["http://127.0.0.1:9947"],
"descriptor": "evm"
}
]
}, {
"name": "zombienet_subnets",
"timeout": 600000,
Expand Down
13 changes: 13 additions & 0 deletions ts-tests/pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,16 @@ onlyBuiltDependencies:
- protobufjs
- sqlite3
- ssh2

# Allow exotic subdependencies (needed for toml dependency)
allowExoticSubdeps: true

allowBuilds:
'@biomejs/biome': set this to true or false
'@parcel/watcher': set this to true or false
cpu-features: set this to true or false
esbuild: set this to true or false
msgpackr-extract: set this to true or false
protobufjs: set this to true or false
sqlite3: set this to true or false
ssh2: set this to true or false
Comment on lines +20 to +28
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Resolve allowBuilds placeholders

The new allowBuilds map still contains generated placeholder strings instead of explicit boolean decisions. pnpm 10.26+ defines this field as a map that explicitly allows (true) or disallows (false) dependency build scripts, so leaving set this to true or false here keeps the new policy unresolved and can cause install-time warnings or stricter future failures. (newreleases.io)

Suggested change
allowBuilds:
'@biomejs/biome': set this to true or false
'@parcel/watcher': set this to true or false
cpu-features: set this to true or false
esbuild: set this to true or false
msgpackr-extract: set this to true or false
protobufjs: set this to true or false
sqlite3: set this to true or false
ssh2: set this to true or false
allowBuilds:
'@biomejs/biome': true
'@parcel/watcher': true
cpu-features: true
esbuild: true
msgpackr-extract: true
protobufjs: true
sqlite3: true
ssh2: true

Comment on lines +20 to +28
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Resolve allowBuilds placeholders

This allowBuilds block still contains generated placeholder values instead of concrete booleans. Since the same packages are already listed under onlyBuiltDependencies, leaving these placeholder strings makes the workspace policy ambiguous and can confuse future pnpm maintenance. Either remove the unfinished block or replace each entry with intentional true/false values.

Comment on lines +20 to +28
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Resolve allowBuilds placeholders

The allowBuilds entries are still literal placeholder strings (set this to true or false). Resolve this before merge by either replacing them with explicit boolean policy values or removing the block if onlyBuiltDependencies above is the intended source of truth. Leaving generated placeholders makes the install policy ambiguous and can confuse future pnpm approve-builds or dependency review work.

Comment on lines +21 to +28
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Resolve allowBuilds placeholders

These entries are still the literal placeholder strings from pnpm approve-builds. Please replace each value with an explicit boolean policy, or remove the allowBuilds block if onlyBuiltDependencies is the intended policy source. Leaving placeholders makes the workspace build policy ambiguous and carries forward the prior review finding.

Comment on lines +21 to +28
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Resolve allowBuilds placeholders

These literal placeholder strings are still committed under allowBuilds. pnpm expects concrete boolean approvals here; leaving set this to true or false makes the build-script policy ambiguous and can break strict install/config validation. Since these packages are already listed in onlyBuiltDependencies, set the corresponding approvals explicitly.

Suggested change
'@biomejs/biome': set this to true or false
'@parcel/watcher': set this to true or false
cpu-features: set this to true or false
esbuild: set this to true or false
msgpackr-extract: set this to true or false
protobufjs: set this to true or false
sqlite3: set this to true or false
ssh2: set this to true or false
'@biomejs/biome': true
'@parcel/watcher': true
cpu-features: true
esbuild: true
msgpackr-extract: true
protobufjs: true
sqlite3: true
ssh2: true

76 changes: 76 additions & 0 deletions ts-tests/suites/zombienet_evm/00-evm-substrate-transfer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { beforeAll, describeSuite, expect } from "@moonwall/cli";
import { subtensor } from "@polkadot-api/descriptors";
import { ethers } from "ethers";
import type { TypedApi } from "polkadot-api";
import { convertH160ToSS58, forceSetBalance, raoToEth, tao, waitForFinalizedBlocks } from "../../utils";

function createEthersWallet(provider: ethers.JsonRpcProvider): ethers.Wallet {
const account = ethers.Wallet.createRandom();
return new ethers.Wallet(account.privateKey, provider);
}

async function estimateTransactionCost(provider: ethers.Provider, tx: ethers.TransactionRequest): Promise<bigint> {
const feeData = await provider.getFeeData();
const estimatedGas = await provider.estimateGas(tx);
const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas;
if (gasPrice == null) {
return estimatedGas;
}
return estimatedGas * gasPrice;
}

describeSuite({
id: "evm-substrate-transfer-basic",
title: "Basic EVM-Substrate Transfer Tests",
foundationMethods: "zombie",
testCases: ({ it, context }) => {
let api: TypedApi<typeof subtensor>;
let ethWallet: ethers.Wallet;
let ethWallet2: ethers.Wallet;

beforeAll(async () => {
api = context.papi("Node").getTypedApi(subtensor);

const provider = context.ethers("EVM").provider as ethers.JsonRpcProvider;
ethWallet = createEthersWallet(provider);
ethWallet2 = createEthersWallet(provider);

await forceSetBalance(api, convertH160ToSS58(ethWallet.address));
await forceSetBalance(api, convertH160ToSS58(ethWallet2.address));
await waitForFinalizedBlocks(api, 1);
}, 120000);

it({
id: "T01",
title: "Can transfer token from EVM to EVM",
test: async () => {
const provider = ethWallet.provider;
if (provider == null) {
throw new Error("ethWallet has no provider");
}

const senderBalanceBefore = await provider.getBalance(ethWallet.address);
const receiverBalanceBefore = await provider.getBalance(ethWallet2.address);

const transferAmount = raoToEth(tao(1));
const tx: ethers.TransactionRequest = {
to: ethWallet2.address,
value: transferAmount,
};

const txFee = await estimateTransactionCost(provider, tx);

const txResponse = await ethWallet.sendTransaction(tx);
const receipt = await txResponse.wait();
expect(receipt).toBeDefined();
expect(receipt!.status).toEqual(1);

const senderBalanceAfter = await provider.getBalance(ethWallet.address);
const receiverBalanceAfter = await provider.getBalance(ethWallet2.address);

expect(senderBalanceAfter).toEqual(senderBalanceBefore - transferAmount - txFee);
expect(receiverBalanceAfter).toEqual(receiverBalanceBefore + transferAmount);
},
});
},
});
28 changes: 28 additions & 0 deletions ts-tests/utils/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { hexToU8a } from "@polkadot/util";
import { blake2AsU8a, encodeAddress } from "@polkadot/util-crypto";

const SS58_PREFIX = 42;

export function convertH160ToPublicKey(ethAddress: string) {
const prefix = "evm:";
const prefixBytes = new TextEncoder().encode(prefix);
const addressBytes = hexToU8a(ethAddress.startsWith("0x") ? ethAddress : `0x${ethAddress}`);
const combined = new Uint8Array(prefixBytes.length + addressBytes.length);

// Concatenate prefix and Ethereum address
combined.set(prefixBytes);
combined.set(addressBytes, prefixBytes.length);

// Hash the combined data (the public key)
const hash = blake2AsU8a(combined);
return hash;
}

export function convertH160ToSS58(ethAddress: string) {
// get the public key
const hash = convertH160ToPublicKey(ethAddress);

// Convert the hash to SS58 format
const ss58Address = encodeAddress(hash, SS58_PREFIX);
return ss58Address;
}
5 changes: 5 additions & 0 deletions ts-tests/utils/balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ export function tao(value: number): bigint {
return TAO * BigInt(value);
}

/** Convert RAO to the EVM native balance unit (1 RAO → 1 gwei on-chain). */
export function raoToEth(rao: bigint): bigint {
return TAO * rao;
}

export async function getBalance(api: TypedApi<typeof subtensor>, ss58Address: string): Promise<bigint> {
const account = await api.query.System.Account.getValue(ss58Address);
return account.data.free;
Expand Down
12 changes: 7 additions & 5 deletions ts-tests/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export * from "./transactions.js";
export * from "./balance.js";
export * from "./subnet.js";
export * from "./staking.js";
export * from "./shield_helpers.ts";
export * from "./account.ts";
export * from "./address.ts";
export * from "./balance.js";
export * from "./coldkey_swap.ts";
export * from "./config.js";
export * from "./shield_helpers.ts";
export * from "./staking.js";
export * from "./subnet.js";
export * from "./transactions.js";
Loading