Skip to content

Commit 358c611

Browse files
authored
Merge pull request #2328 from opentensor/fix/tx-extension-in-precompiles
Involve subtensor tx extension in contracts
2 parents be47139 + dccdc71 commit 358c611

File tree

15 files changed

+406
-87
lines changed

15 files changed

+406
-87
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.

contract-tests/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"scripts": {
3-
"test": "mocha --timeout 999999 --retries 3 --file src/setup.ts --require ts-node/register test/*test.ts"
3+
"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\""
44
},
55
"keywords": [],
66
"author": "",

contract-tests/test/neuron.precompile.reveal-weights.test.ts

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as assert from "assert";
2-
import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate"
2+
import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair, waitForTransactionWithRetry } from "../src/substrate"
33
import { devnet } from "@polkadot-api/descriptors"
44
import { PolkadotSigner, TypedApi } from "polkadot-api";
55
import { convertPublicKeyToSs58, convertH160ToSS58 } from "../src/address-utils"
@@ -23,6 +23,16 @@ const values = [5];
2323
const salt = [9];
2424
const version_key = 0;
2525

26+
async function setStakeThreshold(
27+
api: TypedApi<typeof devnet>,
28+
alice: PolkadotSigner,
29+
minStake: bigint,
30+
) {
31+
const internalCall = api.tx.AdminUtils.sudo_set_stake_threshold({ min_stake: minStake })
32+
const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall })
33+
await waitForTransactionWithRetry(api, tx, alice)
34+
}
35+
2636
function getCommitHash(netuid: number, address: string) {
2737
const registry = new TypeRegistry();
2838
let publicKey = convertH160ToPublicKey(address);
@@ -53,7 +63,7 @@ describe("Test neuron precompile reveal weights", () => {
5363
const coldkey = getRandomSubstrateKeypair();
5464

5565
let api: TypedApi<typeof devnet>
56-
let commitEpoch: number;
66+
let commitEpoch: number | undefined;
5767

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

99+
async function ensureCommitEpoch(netuid: number, contract: ethers.Contract) {
100+
if (commitEpoch !== undefined) {
101+
return
102+
}
103+
104+
const ss58Address = convertH160ToSS58(wallet.address)
105+
const existingCommits = await api.query.SubtensorModule.WeightCommits.getValue(
106+
netuid,
107+
ss58Address
108+
)
109+
if (Array.isArray(existingCommits) && existingCommits.length > 0) {
110+
const entry = existingCommits[0]
111+
const commitBlockRaw =
112+
Array.isArray(entry) && entry.length > 1 ? entry[1] : undefined
113+
const commitBlock =
114+
typeof commitBlockRaw === "bigint"
115+
? Number(commitBlockRaw)
116+
: Number(commitBlockRaw ?? NaN)
117+
if (Number.isFinite(commitBlock)) {
118+
commitEpoch = Math.trunc(commitBlock / (100 + 1))
119+
return
120+
}
121+
}
122+
123+
await setStakeThreshold(api, alice, BigInt(0))
124+
const commitHash = getCommitHash(netuid, wallet.address)
125+
const tx = await contract.commitWeights(netuid, commitHash)
126+
await tx.wait()
127+
128+
const commitBlock = await api.query.System.Number.getValue()
129+
commitEpoch = Math.trunc(commitBlock / (100 + 1))
130+
}
131+
89132
it("EVM neuron commit weights via call precompile", async () => {
90133
let totalNetworks = await api.query.SubtensorModule.TotalNetworks.getValue()
91134
const subnetId = totalNetworks - 1
92135
const commitHash = getCommitHash(subnetId, wallet.address)
93136
const contract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet);
137+
138+
await setStakeThreshold(api, alice, BigInt(1))
139+
await assert.rejects(async () => {
140+
const tx = await contract.commitWeights(subnetId, commitHash)
141+
await tx.wait()
142+
})
143+
await setStakeThreshold(api, alice, BigInt(0))
144+
94145
try {
95146
const tx = await contract.commitWeights(subnetId, commitHash)
96147
await tx.wait()
@@ -120,6 +171,11 @@ describe("Test neuron precompile reveal weights", () => {
120171
// set interval epoch as 1, it is the minimum value now
121172
await setCommitRevealWeightsInterval(api, netuid, BigInt(1))
122173

174+
await ensureCommitEpoch(netuid, contract)
175+
if (commitEpoch === undefined) {
176+
throw new Error("commitEpoch should be set before revealing weights")
177+
}
178+
123179
while (true) {
124180
const currentBlock = await api.query.System.Number.getValue()
125181
const currentEpoch = Math.trunc(currentBlock / (100 + 1))
@@ -130,6 +186,19 @@ describe("Test neuron precompile reveal weights", () => {
130186
await new Promise(resolve => setTimeout(resolve, 1000))
131187
}
132188

189+
await setStakeThreshold(api, alice, BigInt(1))
190+
await assert.rejects(async () => {
191+
const tx = await contract.revealWeights(
192+
netuid,
193+
uids,
194+
values,
195+
salt,
196+
version_key
197+
);
198+
await tx.wait()
199+
})
200+
await setStakeThreshold(api, alice, BigInt(0))
201+
133202
const tx = await contract.revealWeights(
134203
netuid,
135204
uids,

contract-tests/test/subnet.precompile.hyperparameter.test.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import * as assert from "assert";
22

3-
import { getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate"
3+
import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair, waitForTransactionWithRetry } from "../src/substrate"
44
import { devnet } from "@polkadot-api/descriptors"
5-
import { TypedApi } from "polkadot-api";
6-
import { convertPublicKeyToSs58 } from "../src/address-utils"
5+
import { Binary, TypedApi, getTypedCodecs } from "polkadot-api";
6+
import { convertH160ToSS58, convertPublicKeyToSs58 } from "../src/address-utils"
77
import { generateRandomEthersWallet } from "../src/utils";
88
import { ISubnetABI, ISUBNET_ADDRESS } from "../src/contracts/subnet"
99
import { ethers } from "ethers"
@@ -545,4 +545,37 @@ describe("Test the Subnet precompile contract", () => {
545545
assert.equal(valueFromContract, newValue)
546546
assert.equal(valueFromContract, onchainValue);
547547
})
548+
549+
it("Rejects subnet precompile calls when coldkey swap is scheduled (tx extension)", async () => {
550+
const totalNetwork = await api.query.SubtensorModule.TotalNetworks.getValue()
551+
const contract = new ethers.Contract(ISUBNET_ADDRESS, ISubnetABI, wallet);
552+
const netuid = totalNetwork - 1;
553+
554+
const coldkeySs58 = convertH160ToSS58(wallet.address)
555+
const newColdkeySs58 = convertPublicKeyToSs58(hotkey1.publicKey)
556+
const currentBlock = await api.query.System.Number.getValue()
557+
const executionBlock = currentBlock + 10
558+
559+
const codec = await getTypedCodecs(devnet);
560+
const valueBytes = codec.query.SubtensorModule.ColdkeySwapScheduled.value.enc([
561+
executionBlock,
562+
newColdkeySs58,
563+
])
564+
const key = await api.query.SubtensorModule.ColdkeySwapScheduled.getKey(coldkeySs58);
565+
566+
// Use sudo + set_storage since the swap-scheduled check only exists in the tx extension.
567+
const setStorageCall = api.tx.System.set_storage({
568+
items: [[Binary.fromHex(key), Binary.fromBytes(valueBytes)]],
569+
})
570+
const sudoTx = api.tx.Sudo.sudo({ call: setStorageCall.decodedCall })
571+
await waitForTransactionWithRetry(api, sudoTx, getAliceSigner())
572+
573+
const storedValue = await api.query.SubtensorModule.ColdkeySwapScheduled.getValue(coldkeySs58)
574+
assert.deepStrictEqual(storedValue, [executionBlock, newColdkeySs58])
575+
576+
await assert.rejects(async () => {
577+
const tx = await contract.setServingRateLimit(netuid, 100);
578+
await tx.wait();
579+
})
580+
})
548581
})

precompiles/Cargo.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ repository = "https://github.com/opentensor/subtensor/"
1111
targets = ["x86_64-unknown-linux-gnu"]
1212

1313
[dependencies]
14+
# codec.workspace = true
1415
ed25519-dalek = { workspace = true, features = ["alloc"] }
1516
fp-evm.workspace = true
1617
frame-support.workspace = true
@@ -25,6 +26,7 @@ pallet-evm-precompile-simple.workspace = true
2526
pallet-evm-precompile-bn128.workspace = true
2627
pallet-subtensor-proxy.workspace = true
2728
precompile-utils.workspace = true
29+
scale-info.workspace = true
2830
sp-core.workspace = true
2931
sp-io.workspace = true
3032
sp-runtime.workspace = true
@@ -43,24 +45,26 @@ workspace = true
4345
[features]
4446
default = ["std"]
4547
std = [
48+
# "codec/std",
4649
"ed25519-dalek/std",
4750
"fp-evm/std",
4851
"frame-support/std",
4952
"frame-system/std",
5053
"log/std",
5154
"pallet-admin-utils/std",
5255
"pallet-balances/std",
56+
"pallet-crowdloan/std",
57+
"pallet-evm-precompile-bn128/std",
5358
"pallet-evm-precompile-dispatch/std",
5459
"pallet-evm-precompile-modexp/std",
5560
"pallet-evm-precompile-sha3fips/std",
5661
"pallet-evm-precompile-simple/std",
57-
"pallet-evm-precompile-bn128/std",
5862
"pallet-evm/std",
59-
"pallet-crowdloan/std",
6063
"pallet-subtensor-proxy/std",
61-
"pallet-subtensor/std",
6264
"pallet-subtensor-swap/std",
65+
"pallet-subtensor/std",
6366
"precompile-utils/std",
67+
"scale-info/std",
6468
"sp-core/std",
6569
"sp-io/std",
6670
"sp-runtime/std",

precompiles/src/balance_transfer.rs

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,35 @@
11
use core::marker::PhantomData;
22

3-
use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo};
3+
use frame_support::dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo};
4+
use frame_support::traits::IsSubType;
45
use frame_system::RawOrigin;
56
use pallet_evm::PrecompileHandle;
67
use precompile_utils::EvmResult;
78
use sp_core::{H256, U256};
8-
use sp_runtime::traits::{Dispatchable, StaticLookup, UniqueSaturatedInto};
9+
use sp_runtime::traits::{AsSystemOriginSigner, Dispatchable, StaticLookup, UniqueSaturatedInto};
910

1011
use crate::{PrecompileExt, PrecompileHandleExt};
1112

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

1415
impl<R> PrecompileExt<R::AccountId> for BalanceTransferPrecompile<R>
1516
where
16-
R: frame_system::Config + pallet_balances::Config + pallet_evm::Config,
17+
R: frame_system::Config
18+
+ pallet_balances::Config
19+
+ pallet_evm::Config
20+
+ pallet_subtensor::Config
21+
+ Send
22+
+ Sync
23+
+ scale_info::TypeInfo,
1724
R::AccountId: From<[u8; 32]>,
18-
<R as frame_system::Config>::RuntimeCall:
19-
GetDispatchInfo + Dispatchable<PostInfo = PostDispatchInfo>,
25+
<R as frame_system::Config>::RuntimeOrigin: AsSystemOriginSigner<R::AccountId> + Clone,
26+
<R as frame_system::Config>::RuntimeCall: GetDispatchInfo
27+
+ Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
28+
+ IsSubType<pallet_balances::Call<R>>
29+
+ IsSubType<pallet_subtensor::Call<R>>,
2030
<R as frame_system::Config>::RuntimeCall: From<pallet_balances::Call<R>>
2131
+ GetDispatchInfo
22-
+ Dispatchable<PostInfo = PostDispatchInfo>,
32+
+ Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
2333
<<R as frame_system::Config>::Lookup as StaticLookup>::Source: From<R::AccountId>,
2434
<R as pallet_balances::Config>::Balance: TryFrom<U256>,
2535
{
@@ -29,13 +39,22 @@ where
2939
#[precompile_utils::precompile]
3040
impl<R> BalanceTransferPrecompile<R>
3141
where
32-
R: frame_system::Config + pallet_balances::Config + pallet_evm::Config,
42+
R: frame_system::Config
43+
+ pallet_balances::Config
44+
+ pallet_evm::Config
45+
+ pallet_subtensor::Config
46+
+ Send
47+
+ Sync
48+
+ scale_info::TypeInfo,
3349
R::AccountId: From<[u8; 32]>,
34-
<R as frame_system::Config>::RuntimeCall:
35-
GetDispatchInfo + Dispatchable<PostInfo = PostDispatchInfo>,
50+
<R as frame_system::Config>::RuntimeOrigin: AsSystemOriginSigner<R::AccountId> + Clone,
51+
<R as frame_system::Config>::RuntimeCall: GetDispatchInfo
52+
+ Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
53+
+ IsSubType<pallet_balances::Call<R>>
54+
+ IsSubType<pallet_subtensor::Call<R>>,
3655
<R as frame_system::Config>::RuntimeCall: From<pallet_balances::Call<R>>
3756
+ GetDispatchInfo
38-
+ Dispatchable<PostInfo = PostDispatchInfo>,
57+
+ Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
3958
<<R as frame_system::Config>::Lookup as StaticLookup>::Source: From<R::AccountId>,
4059
<R as pallet_balances::Config>::Balance: TryFrom<U256>,
4160
{

precompiles/src/crowdloan.rs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,39 @@ use alloc::string::String;
22
use core::marker::PhantomData;
33

44
use fp_evm::{ExitError, PrecompileFailure};
5-
use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo};
5+
use frame_support::dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo};
6+
use frame_support::traits::IsSubType;
67
use frame_system::RawOrigin;
78
use pallet_evm::AddressMapping;
89
use pallet_evm::PrecompileHandle;
910
use pallet_subtensor_proxy as pallet_proxy;
1011
use precompile_utils::prelude::Address;
1112
use precompile_utils::{EvmResult, solidity::Codec};
1213
use sp_core::{ByteArray, H256};
13-
use sp_runtime::traits::{Dispatchable, UniqueSaturatedInto};
14+
use sp_runtime::traits::{AsSystemOriginSigner, Dispatchable, UniqueSaturatedInto};
1415

1516
use crate::{PrecompileExt, PrecompileHandleExt};
1617

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

1920
impl<R> PrecompileExt<R::AccountId> for CrowdloanPrecompile<R>
2021
where
21-
R: frame_system::Config + pallet_evm::Config + pallet_crowdloan::Config + pallet_proxy::Config,
22+
R: frame_system::Config
23+
+ pallet_balances::Config
24+
+ pallet_crowdloan::Config
25+
+ pallet_evm::Config
26+
+ pallet_proxy::Config
27+
+ pallet_subtensor::Config
28+
+ Send
29+
+ Sync
30+
+ scale_info::TypeInfo,
2231
R::AccountId: From<[u8; 32]> + ByteArray,
32+
<R as frame_system::Config>::RuntimeOrigin: AsSystemOriginSigner<R::AccountId> + Clone,
2333
<R as frame_system::Config>::RuntimeCall: From<pallet_crowdloan::Call<R>>
2434
+ GetDispatchInfo
25-
+ Dispatchable<PostInfo = PostDispatchInfo>,
35+
+ Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
36+
+ IsSubType<pallet_balances::Call<R>>
37+
+ IsSubType<pallet_subtensor::Call<R>>,
2638
<R as pallet_evm::Config>::AddressMapping: AddressMapping<R::AccountId>,
2739
{
2840
const INDEX: u64 = 2057;
@@ -31,14 +43,25 @@ where
3143
#[precompile_utils::precompile]
3244
impl<R> CrowdloanPrecompile<R>
3345
where
34-
R: frame_system::Config + pallet_evm::Config + pallet_crowdloan::Config + pallet_proxy::Config,
46+
R: frame_system::Config
47+
+ pallet_balances::Config
48+
+ pallet_crowdloan::Config
49+
+ pallet_evm::Config
50+
+ pallet_proxy::Config
51+
+ pallet_subtensor::Config
52+
+ Send
53+
+ Sync
54+
+ scale_info::TypeInfo,
3555
R::AccountId: From<[u8; 32]> + ByteArray,
56+
<R as frame_system::Config>::RuntimeOrigin: AsSystemOriginSigner<R::AccountId> + Clone,
3657
<R as frame_system::Config>::RuntimeCall: From<pallet_crowdloan::Call<R>>
3758
+ GetDispatchInfo
38-
+ Dispatchable<PostInfo = PostDispatchInfo>,
59+
+ Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
60+
+ IsSubType<pallet_balances::Call<R>>
61+
+ IsSubType<pallet_subtensor::Call<R>>,
3962
<R as frame_system::Config>::RuntimeCall: From<pallet_crowdloan::Call<R>>
4063
+ GetDispatchInfo
41-
+ Dispatchable<PostInfo = PostDispatchInfo>,
64+
+ Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
4265
<R as pallet_evm::Config>::AddressMapping: AddressMapping<R::AccountId>,
4366
{
4467
#[precompile::public("getCrowdloan(uint32)")]

0 commit comments

Comments
 (0)