Skip to content

Commit 34e4b25

Browse files
authored
Merge pull request #2445 from opentensor/drand_precompile
add drand precompile
2 parents fe74d6a + 608533b commit 34e4b25

File tree

9 files changed

+308
-3
lines changed

9 files changed

+308
-3
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.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Drand precompile address: 0x80e = 2062
2+
export const IDRAND_ADDRESS = "0x000000000000000000000000000000000000080e";
3+
4+
export const IDrandABI = [
5+
{
6+
inputs: [
7+
{
8+
internalType: "uint64",
9+
name: "round",
10+
type: "uint64",
11+
},
12+
],
13+
name: "getRandomness",
14+
outputs: [
15+
{
16+
internalType: "bytes32",
17+
name: "",
18+
type: "bytes32",
19+
},
20+
],
21+
stateMutability: "view",
22+
type: "function",
23+
},
24+
{
25+
inputs: [],
26+
name: "getLastStoredRound",
27+
outputs: [
28+
{
29+
internalType: "uint64",
30+
name: "",
31+
type: "uint64",
32+
},
33+
],
34+
stateMutability: "view",
35+
type: "function",
36+
},
37+
] as const;
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import * as assert from "assert";
2+
3+
import { getDevnetApi } from "../src/substrate";
4+
import { getPublicClient } from "../src/utils";
5+
import { ETH_LOCAL_URL } from "../src/config";
6+
import { devnet } from "@polkadot-api/descriptors";
7+
import { PublicClient } from "viem";
8+
import { TypedApi } from "polkadot-api";
9+
import { toViemAddress } from "../src/address-utils";
10+
import { IDrandABI, IDRAND_ADDRESS } from "../src/contracts/drand";
11+
12+
describe("Test Drand Precompile", () => {
13+
let publicClient: PublicClient;
14+
let api: TypedApi<typeof devnet>;
15+
16+
before(async () => {
17+
publicClient = await getPublicClient(ETH_LOCAL_URL);
18+
api = await getDevnetApi();
19+
});
20+
21+
describe("Drand Randomness Functions", () => {
22+
it("getLastStoredRound returns a value", async () => {
23+
const lastRound = await publicClient.readContract({
24+
abi: IDrandABI,
25+
address: toViemAddress(IDRAND_ADDRESS),
26+
functionName: "getLastStoredRound",
27+
args: [],
28+
});
29+
30+
const lastRoundFromApi = await api.query.Drand.LastStoredRound.getValue({ at: "best" });
31+
32+
assert.ok(lastRound !== undefined, "getLastStoredRound should return a value");
33+
assert.strictEqual(
34+
typeof lastRound,
35+
"bigint",
36+
"getLastStoredRound should return a bigint"
37+
);
38+
assert.ok(lastRound === lastRoundFromApi, "Last stored round should match the value from the API");
39+
});
40+
41+
it("getRandomness returns bytes32 for a round", async () => {
42+
const lastRound = await publicClient.readContract({
43+
abi: IDrandABI,
44+
address: toViemAddress(IDRAND_ADDRESS),
45+
functionName: "getLastStoredRound",
46+
args: [],
47+
});
48+
49+
const randomness = await publicClient.readContract({
50+
abi: IDrandABI,
51+
address: toViemAddress(IDRAND_ADDRESS),
52+
functionName: "getRandomness",
53+
args: [lastRound],
54+
});
55+
56+
const pulseFromApi = await api.query.Drand.Pulses.getValue(lastRound, { at: "best" });
57+
const randomnessFromApi = pulseFromApi?.randomness.asHex();
58+
59+
assert.ok(randomness !== undefined, "getRandomness should return a value");
60+
assert.strictEqual(
61+
typeof randomness,
62+
"string",
63+
"getRandomness should return a hex string (bytes32)"
64+
);
65+
assert.strictEqual(
66+
randomness.length,
67+
66,
68+
"bytes32 should be 0x + 64 hex chars"
69+
);
70+
assert.strictEqual(
71+
randomness,
72+
randomnessFromApi,
73+
"Randomness should match the value from the API"
74+
);
75+
});
76+
77+
it("getRandomness for non-existent round returns zero bytes", async () => {
78+
// Use a very high round number that will not have a stored pulse
79+
const nonExistentRound = BigInt(999999999);
80+
const randomness = await publicClient.readContract({
81+
abi: IDrandABI,
82+
address: toViemAddress(IDRAND_ADDRESS),
83+
functionName: "getRandomness",
84+
args: [nonExistentRound],
85+
});
86+
87+
console.log("randomness", randomness);
88+
89+
assert.ok(randomness !== undefined, "getRandomness should return a value");
90+
const zeroBytes32 = "0x" + "0".repeat(64);
91+
assert.strictEqual(
92+
randomness.toLowerCase(),
93+
zeroBytes32,
94+
"getRandomness for non-existent round should return zero bytes32"
95+
);
96+
});
97+
});
98+
});

pallets/admin-utils/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ pub mod pallet {
147147
AddressMapping,
148148
/// Voting power precompile
149149
VotingPower,
150+
/// Drand randomness precompile
151+
Drand,
150152
}
151153

152154
#[pallet::type_value]

precompiles/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pallet-admin-utils.workspace = true
3939
subtensor-swap-interface.workspace = true
4040
pallet-crowdloan.workspace = true
4141
pallet-shield.workspace = true
42+
pallet-drand.workspace = true
4243

4344
[lints]
4445
workspace = true
@@ -65,6 +66,7 @@ std = [
6566
"pallet-subtensor-swap/std",
6667
"pallet-subtensor/std",
6768
"pallet-shield/std",
69+
"pallet-drand/std",
6870
"precompile-utils/std",
6971
"scale-info/std",
7072
"sp-core/std",

precompiles/src/drand.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use core::marker::PhantomData;
2+
3+
use fp_evm::PrecompileHandle;
4+
use precompile_utils::EvmResult;
5+
use sp_core::H256;
6+
7+
use crate::PrecompileExt;
8+
9+
/// Drand precompile for smart contract access to Drand beacon randomness.
10+
///
11+
/// This precompile allows smart contracts to read verifiable randomness from the
12+
/// Drand Quicknet beacon that is bridged on-chain by the Drand pallet.
13+
pub struct DrandPrecompile<R>(PhantomData<R>);
14+
15+
impl<R> PrecompileExt<R::AccountId> for DrandPrecompile<R>
16+
where
17+
R: frame_system::Config + pallet_drand::Config,
18+
R::AccountId: From<[u8; 32]>,
19+
{
20+
const INDEX: u64 = 2062;
21+
}
22+
23+
#[precompile_utils::precompile]
24+
impl<R> DrandPrecompile<R>
25+
where
26+
R: frame_system::Config + pallet_drand::Config,
27+
R::AccountId: From<[u8; 32]>,
28+
{
29+
/// Get the 32-byte randomness for a specific Drand round.
30+
///
31+
/// Returns the SHA256 hash of the BLS signature for the given round.
32+
/// Returns 32 zero bytes if no pulse exists for the round.
33+
///
34+
/// # Arguments
35+
/// * `round` - The Drand round number (u64)
36+
///
37+
/// # Returns
38+
/// * `bytes32` - The 32-byte randomness, or zeros if round not stored
39+
#[precompile::public("getRandomness(uint64)")]
40+
#[precompile::view]
41+
fn get_randomness(_: &mut impl PrecompileHandle, round: u64) -> EvmResult<H256> {
42+
let randomness = pallet_drand::Pallet::<R>::random_at(round);
43+
Ok(H256::from(randomness))
44+
}
45+
46+
/// Get the last Drand round that has been stored on-chain.
47+
///
48+
/// Returns 0 if no pulses have been stored yet.
49+
///
50+
/// # Returns
51+
/// * `uint64` - The last stored round number
52+
#[precompile::public("getLastStoredRound()")]
53+
#[precompile::view]
54+
fn get_last_stored_round(_: &mut impl PrecompileHandle) -> EvmResult<u64> {
55+
Ok(pallet_drand::LastStoredRound::<R>::get())
56+
}
57+
}

precompiles/src/lib.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub use address_mapping::AddressMappingPrecompile;
3131
pub use alpha::AlphaPrecompile;
3232
pub use balance_transfer::BalanceTransferPrecompile;
3333
pub use crowdloan::CrowdloanPrecompile;
34+
pub use drand::DrandPrecompile;
3435
pub use ed25519::Ed25519Verify;
3536
pub use extensions::PrecompileExt;
3637
pub use leasing::LeasingPrecompile;
@@ -48,6 +49,7 @@ mod address_mapping;
4849
mod alpha;
4950
mod balance_transfer;
5051
mod crowdloan;
52+
mod drand;
5153
mod ed25519;
5254
mod extensions;
5355
mod leasing;
@@ -75,6 +77,7 @@ where
7577
+ pallet_crowdloan::Config
7678
+ pallet_shield::Config
7779
+ pallet_subtensor_proxy::Config
80+
+ pallet_drand::Config
7881
+ Send
7982
+ Sync
8083
+ scale_info::TypeInfo,
@@ -112,6 +115,7 @@ where
112115
+ pallet_crowdloan::Config
113116
+ pallet_shield::Config
114117
+ pallet_subtensor_proxy::Config
118+
+ pallet_drand::Config
115119
+ Send
116120
+ Sync
117121
+ scale_info::TypeInfo,
@@ -136,7 +140,7 @@ where
136140
Self(Default::default())
137141
}
138142

139-
pub fn used_addresses() -> [H160; 27] {
143+
pub fn used_addresses() -> [H160; 28] {
140144
[
141145
hash(1),
142146
hash(2),
@@ -165,6 +169,7 @@ where
165169
hash(VotingPowerPrecompile::<R>::INDEX),
166170
hash(ProxyPrecompile::<R>::INDEX),
167171
hash(AddressMappingPrecompile::<R>::INDEX),
172+
hash(DrandPrecompile::<R>::INDEX),
168173
]
169174
}
170175
}
@@ -180,6 +185,7 @@ where
180185
+ pallet_crowdloan::Config
181186
+ pallet_shield::Config
182187
+ pallet_subtensor_proxy::Config
188+
+ pallet_drand::Config
183189
+ Send
184190
+ Sync
185191
+ scale_info::TypeInfo,
@@ -273,6 +279,9 @@ where
273279
PrecompileEnum::AddressMapping,
274280
)
275281
}
282+
a if a == hash(DrandPrecompile::<R>::INDEX) => {
283+
DrandPrecompile::<R>::try_execute::<R>(handle, PrecompileEnum::Drand)
284+
}
276285
_ => None,
277286
}
278287
}

runtime/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
268268
// `spec_version`, and `authoring_version` are the same between Wasm and native.
269269
// This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use
270270
// the compatible custom types.
271-
spec_version: 391,
271+
spec_version: 392,
272272
impl_version: 1,
273273
apis: RUNTIME_API_VERSIONS,
274274
transaction_version: 1,

0 commit comments

Comments
 (0)