Skip to content

Commit 3bcf9d2

Browse files
committed
Merge branch 'devnet-ready' into get_proxies_in_precompile
2 parents f2ac464 + 6c10711 commit 3bcf9d2

31 files changed

+1132
-874
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ sp-keystore = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "p
274274
w3f-bls = { git = "https://github.com/opentensor/bls", branch = "fix-no-std", default-features = false }
275275
ark-crypto-primitives = { version = "0.4.0", default-features = false }
276276
ark-scale = { version = "0.0.11", default-features = false }
277-
sp-ark-bls12-381 = { git = "https://github.com/paritytech/substrate-curves", default-features = false }
277+
sp-ark-bls12-381 = { git = "https://github.com/paritytech/arkworks-substrate", package = "sp-ark-bls12-381", default-features = false }
278278
ark-bls12-381 = { version = "0.4.0", default-features = false }
279279
ark-serialize = { version = "0.4.0", default-features = false }
280280
ark-ff = { version = "0.4.0", default-features = false }
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.23;
3+
4+
interface ISR25519Verify {
5+
function verify(
6+
bytes32 message,
7+
bytes32 publicKey,
8+
bytes32 r,
9+
bytes32 s
10+
) external pure returns (bool);
11+
}
12+
13+
interface IED25519Verify {
14+
function verify(
15+
bytes32 message,
16+
bytes32 publicKey,
17+
bytes32 r,
18+
bytes32 s
19+
) external pure returns (bool);
20+
}
21+
22+
contract PrecompileGas {
23+
address constant IED25519VERIFY_ADDRESS =
24+
0x0000000000000000000000000000000000000402;
25+
address constant ISR25519VERIFY_ADDRESS =
26+
0x0000000000000000000000000000000000000403;
27+
IED25519Verify constant ed25519 = IED25519Verify(IED25519VERIFY_ADDRESS);
28+
ISR25519Verify constant sr25519 = ISR25519Verify(ISR25519VERIFY_ADDRESS);
29+
30+
event Log(string message);
31+
32+
bytes32 message =
33+
0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef;
34+
bytes32 publicKey =
35+
0x0000000000000000000000000000000000000000000000000000000000000000;
36+
bytes32 r =
37+
0x0000000000000000000000000000000000000000000000000000000000000000;
38+
bytes32 s =
39+
0x0000000000000000000000000000000000000000000000000000000000000000;
40+
41+
/**
42+
* @notice Call the precompile using hardcoded signature data
43+
* @param iterations Number of times to call the precompile
44+
*/
45+
function callED25519(uint64 iterations) external {
46+
for (uint64 i = 0; i < iterations; i++) {
47+
ed25519.verify(message, publicKey, r, s);
48+
}
49+
emit Log("callED25519");
50+
}
51+
52+
/**
53+
* @notice Call the precompile using hardcoded signature data
54+
* @param iterations Number of times to call the precompile
55+
*/
56+
function callSR25519(uint64 iterations) external {
57+
for (uint64 i = 0; i < iterations; i++) {
58+
sr25519.verify(message, publicKey, r, s);
59+
}
60+
emit Log("callSR25519");
61+
}
62+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
2+
export const PrecompileGas_CONTRACT_ABI = [
3+
{
4+
"anonymous": false,
5+
"inputs": [
6+
{
7+
"indexed": false,
8+
"internalType": "string",
9+
"name": "message",
10+
"type": "string"
11+
}
12+
],
13+
"name": "Log",
14+
"type": "event"
15+
},
16+
{
17+
"inputs": [
18+
{
19+
"internalType": "uint64",
20+
"name": "iterations",
21+
"type": "uint64"
22+
}
23+
],
24+
"name": "callED25519",
25+
"outputs": [],
26+
"stateMutability": "nonpayable",
27+
"type": "function"
28+
},
29+
{
30+
"inputs": [
31+
{
32+
"internalType": "uint64",
33+
"name": "iterations",
34+
"type": "uint64"
35+
}
36+
],
37+
"name": "callSR25519",
38+
"outputs": [],
39+
"stateMutability": "nonpayable",
40+
"type": "function"
41+
}
42+
]
43+
44+
export const PrecompileGas_CONTRACT_BYTECODE = "60806040527f1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef5f1b5f555f5f1b6001555f5f1b6002555f5f1b6003553480156045575f5ffd5b5061048b806100535f395ff3fe608060405234801561000f575f5ffd5b5060043610610034575f3560e01c806356554a5714610038578063bd9cac2b14610054575b5f5ffd5b610052600480360381019061004d919061028f565b610070565b005b61006e6004803603810190610069919061028f565b61015f565b005b5f5f90505b8167ffffffffffffffff168167ffffffffffffffff1610156101265761040373ffffffffffffffffffffffffffffffffffffffff1663869adcb95f546001546002546003546040518563ffffffff1660e01b81526004016100d994939291906102d2565b602060405180830381865afa1580156100f4573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610118919061034a565b508080600101915050610075565b507fcf34ef537ac33ee1ac626ca1587a0a7e8e51561e5514f8cb36afa1c5102b3bab604051610154906103cf565b60405180910390a150565b5f5f90505b8167ffffffffffffffff168167ffffffffffffffff1610156102155761040273ffffffffffffffffffffffffffffffffffffffff1663869adcb95f546001546002546003546040518563ffffffff1660e01b81526004016101c894939291906102d2565b602060405180830381865afa1580156101e3573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610207919061034a565b508080600101915050610164565b507fcf34ef537ac33ee1ac626ca1587a0a7e8e51561e5514f8cb36afa1c5102b3bab60405161024390610437565b60405180910390a150565b5f5ffd5b5f67ffffffffffffffff82169050919050565b61026e81610252565b8114610278575f5ffd5b50565b5f8135905061028981610265565b92915050565b5f602082840312156102a4576102a361024e565b5b5f6102b18482850161027b565b91505092915050565b5f819050919050565b6102cc816102ba565b82525050565b5f6080820190506102e55f8301876102c3565b6102f260208301866102c3565b6102ff60408301856102c3565b61030c60608301846102c3565b95945050505050565b5f8115159050919050565b61032981610315565b8114610333575f5ffd5b50565b5f8151905061034481610320565b92915050565b5f6020828403121561035f5761035e61024e565b5b5f61036c84828501610336565b91505092915050565b5f82825260208201905092915050565b7f63616c6c535232353531390000000000000000000000000000000000000000005f82015250565b5f6103b9600b83610375565b91506103c482610385565b602082019050919050565b5f6020820190508181035f8301526103e6816103ad565b9050919050565b7f63616c6c454432353531390000000000000000000000000000000000000000005f82015250565b5f610421600b83610375565b915061042c826103ed565b602082019050919050565b5f6020820190508181035f83015261044e81610415565b905091905056fea26469706673582212202addcdae9c59ee78157cddedc3148678edf455132bdfc62347f85e7c660b4d2164736f6c634300081e0033"

evm-tests/src/substrate.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ export function getRandomSubstrateKeypair() {
7575
return hdkdKeyPair
7676
}
7777

78-
export async function getBalance(api: TypedApi<typeof devnet>) {
79-
const value = await api.query.Balances.Account.getValue("")
80-
return value
78+
export async function getBalance(api: TypedApi<typeof devnet>, ss58Address: string) {
79+
const value = await api.query.System.Account.getValue(ss58Address)
80+
return value.data.free
8181
}
8282

8383
export async function getNonce(api: TypedApi<typeof devnet>, ss58Address: string): Promise<number> {
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import * as assert from "assert";
2+
import { generateRandomEthersWallet, getPublicClient } from "../src/utils";
3+
import { ETH_LOCAL_URL } from "../src/config";
4+
import { getBalance, getDevnetApi } from "../src/substrate";
5+
import { forceSetBalanceToEthAddress } from "../src/subtensor";
6+
import { PrecompileGas_CONTRACT_ABI, PrecompileGas_CONTRACT_BYTECODE } from "../src/contracts/precompileGas";
7+
import { ethers } from "ethers";
8+
import { TypedApi } from "polkadot-api";
9+
import { devnet } from "@polkadot-api/descriptors";
10+
import { disableWhiteListCheck } from "../src/subtensor";
11+
import { convertH160ToSS58, convertPublicKeyToSs58 } from "../src/address-utils";
12+
13+
describe("SR25519 ED25519 Precompile Gas Test", () => {
14+
const wallet = generateRandomEthersWallet();
15+
let api: TypedApi<typeof devnet>;
16+
17+
// scope of precompile gas usage for sr25519 and ed25519
18+
const minPrecompileGas = BigInt(6000);
19+
const maxPrecompileGas = BigInt(10000);
20+
21+
before(async () => {
22+
api = await getDevnetApi();
23+
await forceSetBalanceToEthAddress(api, wallet.address);
24+
await disableWhiteListCheck(api, true);
25+
});
26+
27+
it("Can deploy and call attackHardcoded", async () => {
28+
const fee = await api.query.BaseFee.BaseFeePerGas.getValue()
29+
assert.ok(fee[0] > 1000000000);
30+
const baseFee = BigInt(fee[0]) / BigInt(1000000000);
31+
console.log("Base fee per gas:", baseFee);
32+
33+
const contractFactory = new ethers.ContractFactory(PrecompileGas_CONTRACT_ABI, PrecompileGas_CONTRACT_BYTECODE, wallet);
34+
const contractDeploy = await contractFactory.deploy();
35+
36+
const result = await contractDeploy.waitForDeployment();
37+
console.log("Contract deployed to:", result.target);
38+
39+
40+
let oneIterationGas = BigInt(0);
41+
42+
for (const iter of [1, 11, 101]) {
43+
const balanceBefore = await getBalance(api, convertH160ToSS58(wallet.address));
44+
const contract = new ethers.Contract(result.target, PrecompileGas_CONTRACT_ABI, wallet);
45+
const iterations = iter;
46+
const tx = await contract.callED25519(iterations)
47+
await tx.wait()
48+
49+
const balanceAfter = await getBalance(api, convertH160ToSS58(wallet.address));
50+
assert.ok(balanceAfter < balanceBefore);
51+
52+
const usedGas = balanceBefore - balanceAfter;
53+
if (iterations === 1) {
54+
oneIterationGas = usedGas;
55+
continue;
56+
}
57+
58+
assert.ok(usedGas >= oneIterationGas);
59+
60+
const precompileUsedGas = BigInt(usedGas - oneIterationGas);
61+
assert.ok(precompileUsedGas >= minPrecompileGas * BigInt(iterations - 1) * baseFee);
62+
assert.ok(precompileUsedGas <= maxPrecompileGas * BigInt(iterations - 1) * baseFee);
63+
}
64+
65+
for (const iter of [1, 11, 101]) {
66+
const balanceBefore = await getBalance(api, convertH160ToSS58(wallet.address));
67+
const contract = new ethers.Contract(result.target, PrecompileGas_CONTRACT_ABI, wallet);
68+
const iterations = iter;
69+
const tx = await contract.callSR25519(iterations)
70+
await tx.wait()
71+
72+
const balanceAfter = await getBalance(api, convertH160ToSS58(wallet.address));
73+
assert.ok(balanceAfter < balanceBefore);
74+
75+
const usedGas = balanceBefore - balanceAfter;
76+
if (iterations === 1) {
77+
oneIterationGas = usedGas;
78+
continue;
79+
}
80+
81+
assert.ok(usedGas >= oneIterationGas);
82+
83+
const precompileUsedGas = BigInt(usedGas - oneIterationGas);
84+
assert.ok(precompileUsedGas >= minPrecompileGas * BigInt(iterations - 1) * baseFee);
85+
assert.ok(precompileUsedGas <= maxPrecompileGas * BigInt(iterations - 1) * baseFee);
86+
}
87+
});
88+
});

node/src/mev_shield/author.rs

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,6 @@ where
337337
traits::{ConstU32, TransactionExtension},
338338
};
339339

340-
// Helper: map a Block hash to H256
341340
fn to_h256<H: AsRef<[u8]>>(h: H) -> H256 {
342341
let bytes = h.as_ref();
343342
let mut out = [0u8; 32];
@@ -356,7 +355,7 @@ where
356355
dst.copy_from_slice(src);
357356
H256(out)
358357
} else {
359-
// Extremely unlikely; fall back to zeroed H256 if indices are somehow invalid.
358+
// Extremely defensive fallback.
360359
H256([0u8; 32])
361360
}
362361
}
@@ -365,9 +364,10 @@ where
365364
let public_key: BoundedVec<u8, MaxPk> = BoundedVec::try_from(next_public_key)
366365
.map_err(|_| anyhow::anyhow!("public key too long (>2048 bytes)"))?;
367366

368-
// 1) The runtime call carrying public key bytes.
367+
// 1) Runtime call carrying the public key bytes.
369368
let call = RuntimeCall::MevShield(pallet_shield::Call::announce_next_key { public_key });
370369

370+
// 2) Build the transaction extensions exactly like the runtime.
371371
type Extra = runtime::TransactionExtensions;
372372
let extra: Extra =
373373
(
@@ -390,42 +390,44 @@ where
390390
frame_metadata_hash_extension::CheckMetadataHash::<runtime::Runtime>::new(false),
391391
);
392392

393+
// 3) Manually construct the `Implicit` tuple that the runtime will also derive.
393394
type Implicit = <Extra as TransactionExtension<RuntimeCall>>::Implicit;
394395

395396
let info = client.info();
396397
let genesis_h256: H256 = to_h256(info.genesis_hash);
397398

398399
let implicit: Implicit = (
399400
(), // CheckNonZeroSender
400-
runtime::VERSION.spec_version, // CheckSpecVersion
401-
runtime::VERSION.transaction_version, // CheckTxVersion
402-
genesis_h256, // CheckGenesis
403-
genesis_h256, // CheckEra (Immortal)
404-
(), // CheckNonce (additional part)
405-
(), // CheckWeight
406-
(), // ChargeTransactionPaymentWrapper (additional part)
407-
(), // SubtensorTransactionExtension (additional part)
408-
(), // DrandPriority
409-
None, // CheckMetadataHash (disabled)
401+
runtime::VERSION.spec_version, // CheckSpecVersion::Implicit = u32
402+
runtime::VERSION.transaction_version, // CheckTxVersion::Implicit = u32
403+
genesis_h256, // CheckGenesis::Implicit = Hash
404+
genesis_h256, // CheckEra::Implicit (Immortal => genesis hash)
405+
(), // CheckNonce::Implicit = ()
406+
(), // CheckWeight::Implicit = ()
407+
(), // ChargeTransactionPaymentWrapper::Implicit = ()
408+
(), // SubtensorTransactionExtension::Implicit = ()
409+
(), // DrandPriority::Implicit = ()
410+
None, // CheckMetadataHash::Implicit = Option<[u8; 32]>
410411
);
411412

412-
// Build the exact signable payload.
413+
// 4) Build the exact signable payload from call + extra + implicit.
413414
let payload: SignedPayload = SignedPayload::from_raw(call.clone(), extra.clone(), implicit);
414415

415-
let raw_payload = payload.encode();
416-
417-
// Sign with the local Aura key.
418-
let sig_opt = keystore
419-
.sr25519_sign(AURA_KEY_TYPE, &aura_pub, &raw_payload)
416+
// 5) Sign with the local Aura key using the same SCALE bytes the runtime expects.
417+
let sig_opt = payload
418+
.using_encoded(|bytes| keystore.sr25519_sign(AURA_KEY_TYPE, &aura_pub, bytes))
420419
.map_err(|e| anyhow::anyhow!("keystore sr25519_sign error: {e:?}"))?;
420+
421421
let sig = sig_opt
422422
.ok_or_else(|| anyhow::anyhow!("keystore sr25519_sign returned None for Aura key"))?;
423423

424424
let signature: MultiSignature = sig.into();
425425

426+
// 6) Sender address = AccountId32 derived from the Aura sr25519 public key.
426427
let who: AccountId32 = aura_pub.into();
427428
let address = sp_runtime::MultiAddress::Id(who);
428429

430+
// 7) Assemble the signed extrinsic and submit it to the pool.
429431
let uxt: UncheckedExtrinsic = UncheckedExtrinsic::new_signed(call, address, signature, extra);
430432

431433
let xt_bytes = uxt.encode();

0 commit comments

Comments
 (0)