Skip to content

Commit cd49565

Browse files
committed
fix low gas fee for precompile
1 parent 5126a38 commit cd49565

File tree

6 files changed

+202
-10
lines changed

6 files changed

+202
-10
lines changed
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+
});

precompiles/src/ed25519.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ impl<A> LinearCostPrecompile for Ed25519Verify<A>
2121
where
2222
A: From<[u8; 32]>,
2323
{
24-
const BASE: u64 = 15;
25-
const WORD: u64 = 3;
24+
const BASE: u64 = 6000;
25+
const WORD: u64 = 0;
2626

2727
fn execute(input: &[u8], _: u64) -> Result<(ExitSucceed, Vec<u8>), PrecompileFailure> {
2828
if input.len() < 132 {

precompiles/src/sr25519.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,15 @@ impl<A> LinearCostPrecompile for Sr25519Verify<A>
2222
where
2323
A: From<[u8; 32]>,
2424
{
25-
const BASE: u64 = 15;
26-
const WORD: u64 = 3;
25+
const BASE: u64 = 6000;
26+
const WORD: u64 = 0;
2727

2828
fn execute(input: &[u8], _: u64) -> Result<(ExitSucceed, Vec<u8>), PrecompileFailure> {
2929
if input.len() < 132 {
3030
return Err(PrecompileFailure::Error {
3131
exit_status: ExitError::Other("input must contain 128 bytes".into()),
3232
});
33-
};
34-
33+
}
3534
let mut buf = [0u8; 32];
3635

3736
let msg = parse_slice(input, 4, 36)?;
@@ -50,7 +49,6 @@ where
5049
if valid {
5150
buf[31] = 1u8;
5251
}
53-
5452
Ok((ExitSucceed::Returned, buf.to_vec()))
5553
}
5654
}

0 commit comments

Comments
 (0)