Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions contract-tests/src/contracts/drand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Drand precompile address: 0x80e = 2062
export const IDRAND_ADDRESS = "0x000000000000000000000000000000000000080e";

export const IDrandABI = [
{
inputs: [
{
internalType: "uint64",
name: "round",
type: "uint64",
},
],
name: "getRandomness",
outputs: [
{
internalType: "bytes32",
name: "",
type: "bytes32",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "getLastStoredRound",
outputs: [
{
internalType: "uint64",
name: "",
type: "uint64",
},
],
stateMutability: "view",
type: "function",
},
] as const;
98 changes: 98 additions & 0 deletions contract-tests/test/drand.precompile.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import * as assert from "assert";

import { getDevnetApi } from "../src/substrate";
import { getPublicClient } from "../src/utils";
import { ETH_LOCAL_URL } from "../src/config";
import { devnet } from "@polkadot-api/descriptors";
import { PublicClient } from "viem";
import { TypedApi } from "polkadot-api";
import { toViemAddress } from "../src/address-utils";
import { IDrandABI, IDRAND_ADDRESS } from "../src/contracts/drand";

describe("Test Drand Precompile", () => {
let publicClient: PublicClient;
let api: TypedApi<typeof devnet>;

before(async () => {
publicClient = await getPublicClient(ETH_LOCAL_URL);
api = await getDevnetApi();
});

describe("Drand Randomness Functions", () => {
it("getLastStoredRound returns a value", async () => {
const lastRound = await publicClient.readContract({
abi: IDrandABI,
address: toViemAddress(IDRAND_ADDRESS),
functionName: "getLastStoredRound",
args: [],
});

const lastRoundFromApi = await api.query.Drand.LastStoredRound.getValue({ at: "best" });

assert.ok(lastRound !== undefined, "getLastStoredRound should return a value");
assert.strictEqual(
typeof lastRound,
"bigint",
"getLastStoredRound should return a bigint"
);
assert.ok(lastRound === lastRoundFromApi, "Last stored round should match the value from the API");
});

it("getRandomness returns bytes32 for a round", async () => {
const lastRound = await publicClient.readContract({
abi: IDrandABI,
address: toViemAddress(IDRAND_ADDRESS),
functionName: "getLastStoredRound",
args: [],
});

const randomness = await publicClient.readContract({
abi: IDrandABI,
address: toViemAddress(IDRAND_ADDRESS),
functionName: "getRandomness",
args: [lastRound],
});

const pulseFromApi = await api.query.Drand.Pulses.getValue(lastRound, { at: "best" });
const randomnessFromApi = pulseFromApi?.randomness.asHex();

assert.ok(randomness !== undefined, "getRandomness should return a value");
assert.strictEqual(
typeof randomness,
"string",
"getRandomness should return a hex string (bytes32)"
);
assert.strictEqual(
randomness.length,
66,
"bytes32 should be 0x + 64 hex chars"
);
assert.strictEqual(
randomness,
randomnessFromApi,
"Randomness should match the value from the API"
);
});

it("getRandomness for non-existent round returns zero bytes", async () => {
// Use a very high round number that will not have a stored pulse
const nonExistentRound = BigInt(999999999);
const randomness = await publicClient.readContract({
abi: IDrandABI,
address: toViemAddress(IDRAND_ADDRESS),
functionName: "getRandomness",
args: [nonExistentRound],
});

console.log("randomness", randomness);

assert.ok(randomness !== undefined, "getRandomness should return a value");
const zeroBytes32 = "0x" + "0".repeat(64);
assert.strictEqual(
randomness.toLowerCase(),
zeroBytes32,
"getRandomness for non-existent round should return zero bytes32"
);
});
});
});
2 changes: 2 additions & 0 deletions pallets/admin-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ pub mod pallet {
AddressMapping,
/// Voting power precompile
VotingPower,
/// Drand randomness precompile
Drand,
}

#[pallet::type_value]
Expand Down
2 changes: 2 additions & 0 deletions precompiles/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pallet-admin-utils.workspace = true
subtensor-swap-interface.workspace = true
pallet-crowdloan.workspace = true
pallet-shield.workspace = true
pallet-drand.workspace = true

[lints]
workspace = true
Expand All @@ -65,6 +66,7 @@ std = [
"pallet-subtensor-swap/std",
"pallet-subtensor/std",
"pallet-shield/std",
"pallet-drand/std",
"precompile-utils/std",
"scale-info/std",
"sp-core/std",
Expand Down
57 changes: 57 additions & 0 deletions precompiles/src/drand.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use core::marker::PhantomData;

use fp_evm::PrecompileHandle;
use precompile_utils::EvmResult;
use sp_core::H256;

use crate::PrecompileExt;

/// Drand precompile for smart contract access to Drand beacon randomness.
///
/// This precompile allows smart contracts to read verifiable randomness from the
/// Drand Quicknet beacon that is bridged on-chain by the Drand pallet.
pub struct DrandPrecompile<R>(PhantomData<R>);

impl<R> PrecompileExt<R::AccountId> for DrandPrecompile<R>
where
R: frame_system::Config + pallet_drand::Config,
R::AccountId: From<[u8; 32]>,
{
const INDEX: u64 = 2062;
}

#[precompile_utils::precompile]
impl<R> DrandPrecompile<R>
where
R: frame_system::Config + pallet_drand::Config,
R::AccountId: From<[u8; 32]>,
{
/// Get the 32-byte randomness for a specific Drand round.
///
/// Returns the SHA256 hash of the BLS signature for the given round.
/// Returns 32 zero bytes if no pulse exists for the round.
///
/// # Arguments
/// * `round` - The Drand round number (u64)
///
/// # Returns
/// * `bytes32` - The 32-byte randomness, or zeros if round not stored
#[precompile::public("getRandomness(uint64)")]
#[precompile::view]
fn get_randomness(_: &mut impl PrecompileHandle, round: u64) -> EvmResult<H256> {
let randomness = pallet_drand::Pallet::<R>::random_at(round);
Ok(H256::from(randomness))
}

/// Get the last Drand round that has been stored on-chain.
///
/// Returns 0 if no pulses have been stored yet.
///
/// # Returns
/// * `uint64` - The last stored round number
#[precompile::public("getLastStoredRound()")]
#[precompile::view]
fn get_last_stored_round(_: &mut impl PrecompileHandle) -> EvmResult<u64> {
Ok(pallet_drand::LastStoredRound::<R>::get())
}
}
11 changes: 10 additions & 1 deletion precompiles/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub use address_mapping::AddressMappingPrecompile;
pub use alpha::AlphaPrecompile;
pub use balance_transfer::BalanceTransferPrecompile;
pub use crowdloan::CrowdloanPrecompile;
pub use drand::DrandPrecompile;
pub use ed25519::Ed25519Verify;
pub use extensions::PrecompileExt;
pub use leasing::LeasingPrecompile;
Expand All @@ -48,6 +49,7 @@ mod address_mapping;
mod alpha;
mod balance_transfer;
mod crowdloan;
mod drand;
mod ed25519;
mod extensions;
mod leasing;
Expand Down Expand Up @@ -75,6 +77,7 @@ where
+ pallet_crowdloan::Config
+ pallet_shield::Config
+ pallet_subtensor_proxy::Config
+ pallet_drand::Config
+ Send
+ Sync
+ scale_info::TypeInfo,
Expand Down Expand Up @@ -112,6 +115,7 @@ where
+ pallet_crowdloan::Config
+ pallet_shield::Config
+ pallet_subtensor_proxy::Config
+ pallet_drand::Config
+ Send
+ Sync
+ scale_info::TypeInfo,
Expand All @@ -136,7 +140,7 @@ where
Self(Default::default())
}

pub fn used_addresses() -> [H160; 27] {
pub fn used_addresses() -> [H160; 28] {
[
hash(1),
hash(2),
Expand Down Expand Up @@ -165,6 +169,7 @@ where
hash(VotingPowerPrecompile::<R>::INDEX),
hash(ProxyPrecompile::<R>::INDEX),
hash(AddressMappingPrecompile::<R>::INDEX),
hash(DrandPrecompile::<R>::INDEX),
]
}
}
Expand All @@ -180,6 +185,7 @@ where
+ pallet_crowdloan::Config
+ pallet_shield::Config
+ pallet_subtensor_proxy::Config
+ pallet_drand::Config
+ Send
+ Sync
+ scale_info::TypeInfo,
Expand Down Expand Up @@ -273,6 +279,9 @@ where
PrecompileEnum::AddressMapping,
)
}
a if a == hash(DrandPrecompile::<R>::INDEX) => {
DrandPrecompile::<R>::try_execute::<R>(handle, PrecompileEnum::Drand)
}
_ => None,
}
}
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// `spec_version`, and `authoring_version` are the same between Wasm and native.
// This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use
// the compatible custom types.
spec_version: 380,
spec_version: 381,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
Expand Down
Loading
Loading