Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
d9a7145
fix(node): drop isEthereum from testnet properties
nol4lej Aug 24, 2026
3212c05
fix(rpc): derive state-override account keys the way the runtime does
nol4lej Aug 24, 2026
9e32354
fix(dev): endow the EVM relay key so relaying works out of the box
nol4lej Aug 24, 2026
0f38fab
test(web3api): read the runtime version instead of hardcoding it
nol4lej Aug 24, 2026
fd11165
test(transaction-cost): sign the transaction so it reaches the check …
nol4lej Aug 24, 2026
3ae87f9
feat(ismp): integrate Hyperbridge for cross-chain messaging with Paseo
nol4lej Aug 31, 2026
4bebcef
test(ismp): add the ISMP test suites and Hyperbridge relayer kit
nol4lej Aug 31, 2026
0563e19
build(sdk): migrate polkadot-sdk 2512 -> 2606 and ISMP to republished…
nol4lej Sep 1, 2026
b09e53f
test(frontier): drop vendored upstream test harnesses to resolve the …
nol4lej Sep 1, 2026
3733fe2
fix(frontier): apply portable upstream security and correctness patches
nol4lej Sep 1, 2026
c245f99
build: drop dead core2 patch, its cid -> sc-network chain is gone sin…
nol4lej Sep 1, 2026
7c747a9
chore: drop obsolete advisory ignores, dead weight-reclaim wiring and…
nol4lej Sep 1, 2026
600a82e
docs: correct post-migration comments and record the deliberate ISMP …
nol4lej Sep 1, 2026
8510ca6
docs(runtime): cut node-template boilerplate comments and state the S…
nol4lej Sep 1, 2026
90f5411
docs(ismp): drop migration archaeology, dead references and duplicate…
nol4lej Sep 1, 2026
b07353e
feat(ismp): enforce the slot-duration bounds at compile time and expo…
nol4lej Sep 1, 2026
d0b45a5
style: restore canonical rustfmt and taplo formatting after the comme…
nol4lej Sep 1, 2026
b98876e
style: sort dependency tables alphabetically to satisfy taplo
nol4lej Sep 1, 2026
237eb7f
fix(benchmarks): drop the removed pallet_ismp target and make the run…
nol4lej Sep 1, 2026
74491b2
fix(benchmarks): report a SIGKILLed pallet as a failure instead of cl…
nol4lej Sep 1, 2026
23c65fe
fix(benchmarks): skip the storage-info dump that keeps runs in the OO…
nol4lej Sep 1, 2026
0b821e9
refactor(scripts): group the benchmark tooling under scripts/benchmarks
nol4lej Sep 1, 2026
a687672
fix(benchmarks): refuse to merge fewer parts than the target file alr…
nol4lej Sep 1, 2026
7545480
fix(benchmarks): correct the misleading --allow-partial guidance in t…
nol4lej Sep 2, 2026
67a30b9
refactor(benchmarks): restore the plain runner and point it at the re…
nol4lej Sep 2, 2026
7de1128
refactor(benchmarks): restore the plain runner and drop the low-memor…
nol4lej Sep 2, 2026
09fd1d0
perf(ismp): replace hand-written messaging weights with CPX62 benchmarks
nol4lej Sep 2, 2026
ca11f05
fix(ismp-messaging): correct the overflowed dispatch_post proof size …
nol4lej Sep 2, 2026
c12dba0
build(runtime): bump spec_version to 11 for the ISMP runtime upgrade
nol4lej Sep 2, 2026
9df1113
ci(release): verify the Docker image's coprocessor before pushing the…
nol4lej Sep 2, 2026
d57ff9a
docs(ismp-messaging): correct the proof_size root cause and record th…
nol4lej Sep 2, 2026
22003a1
chore(evm-polkavm): align polkavm to 0.33.1, enforce the sbrk-free IS…
nol4lej Sep 3, 2026
4d4172b
Merge branch 'main' of github.com:orbinum/node into chore/polkavm-0.33
nol4lej Sep 3, 2026
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
12 changes: 12 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ jobs:
run: make build-release
# Builds node binary AND runtime WASM (no need to rebuild separately)

- name: Check the PolkaVM guest ABI
run: |
# `frame/evm-polkavm/uapi` compiles its host-function ABI under
# `cfg(target_arch = "riscv64")`. Nothing else in the workspace builds for that
# target, so this half went unchecked from the commit that added it until
# someone tried by hand — and it did not compile. The target is installed here
# rather than in rust-toolchain.toml so a normal `cargo build` does not pay for
# a ~100 MB std nobody else needs.
rustup target add riscv64imac-unknown-none-elf
cargo check -p pallet-evm-polkavm-uapi \
--target riscv64imac-unknown-none-elf

- name: Upload node binary
uses: actions/upload-artifact@v4
with:
Expand Down
56 changes: 9 additions & 47 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion frame/evm-polkavm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pallet-evm = { workspace = true }
# PolkaVM executor
pallet-evm-polkavm-proc-macro = { workspace = true }
pallet-evm-polkavm-uapi = { workspace = true, features = ["scale"] }
polkavm = { version = "0.29.1", default-features = false }
polkavm = { version = "0.33.1", default-features = false }

[features]
default = ["std"]
Expand Down
20 changes: 17 additions & 3 deletions frame/evm-polkavm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl<Inner: PrecompileSet, T: Config> PrecompileSet for PolkaVmSet<Inner, T> {
) -> Option<Result<PrecompileOutput, PrecompileFailure>> {
let code_address = handle.code_address();
let code = pallet_evm::AccountCodes::<T>::get(code_address);
if code[0..8] == vm::PREFIX {
if code.get(0..8) == Some(&vm::PREFIX[..]) {
let mut run = || {
let prepared_call: vm::PreparedCall<'_, T, _> = vm::PreparedCall::load(handle)?;
prepared_call.call()
Expand Down Expand Up @@ -90,7 +90,7 @@ impl<Inner: PrecompileSet, T: Config> PrecompileSet for PolkaVmSet<Inner, T> {

fn is_precompile(&self, address: H160, remaining_gas: u64) -> IsPrecompileResult {
let code = pallet_evm::AccountCodes::<T>::get(address);
if code[0..8] == vm::PREFIX {
if code.get(0..8) == Some(&vm::PREFIX[..]) {
IsPrecompileResult::Answer {
is_precompile: true,
extra_cost: 0,
Expand Down Expand Up @@ -134,6 +134,14 @@ pub mod pallet {
NotPolkaVmContract,
/// Contract already exist in state.
AlreadyExist,
/// The code is not a parseable PolkaVM program blob.
InvalidProgramBlob,
/// The blob declares an instruction set this chain does not accept.
///
/// Only `ReviveV1` and `JamV1` are allowed. The `Latest32`/`Latest64` sets include
/// the `sbrk` opcode, which lets a contract grow its heap at run time — memory
/// growth changes gas consumption, and this is consensus code.
UnsupportedInstructionSet,
}

#[pallet::call]
Expand All @@ -150,10 +158,16 @@ pub mod pallet {
return Err(Error::<T>::MaxCodeSizeExceeded.into());
}

if code[0..8] != crate::vm::PREFIX {
if code.get(0..8) != Some(&crate::vm::PREFIX[..]) {
return Err(Error::<T>::NotPolkaVmContract.into());
}

let blob = polkavm::ProgramBlob::parse(code[8..].to_vec().into())
.map_err(|_| Error::<T>::InvalidProgramBlob)?;
if !crate::vm::is_accepted_isa(blob.isa()) {
return Err(Error::<T>::UnsupportedInstructionSet.into());
}

let caller = ensure_signed(origin)?;
let address =
<T as Config>::CreateAddressScheme::create_address_scheme(caller, &code[..], salt);
Expand Down
111 changes: 110 additions & 1 deletion frame/evm-polkavm/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,25 @@ pub use self::runtime::{ExecResult, Runtime, RuntimeCosts, SupervisorError};
pub const PREFIX: [u8; 8] = [0xef, 0x70, 0x6F, 0x6C, 0x6B, 0x61, 0x76, 0x6D];
pub const CALL_IDENTIFIER: &str = "call";
pub const PAGE_SIZE: u32 = 4 * 1024;

/// Whether this chain accepts contracts compiled for `isa`.
///
/// Until polkavm 0.30 the host refused `sbrk` with `ModuleConfig::set_allow_sbrk(false)`.
/// That knob is gone: the blob's own instruction set now decides whether the opcode
/// decodes, and the linker takes the ISA as an explicit argument, so the choice belongs
/// to whoever built the contract. `Latest32`/`Latest64` include `sbrk` (opcode 101);
/// `ReviveV1` and `JamV1` do not.
///
/// A growable heap changes gas consumption, and this runs in consensus, so the answer is
/// a whitelist rather than a blocklist: a future ISA is rejected until someone checks it.
pub fn is_accepted_isa(isa: polkavm::program::InstructionSetKind) -> bool {
use polkavm::program::InstructionSetKind as Isa;
match isa {
Isa::ReviveV1 | Isa::JamV1 => true,
Isa::Latest32 | Isa::Latest64 => false,
}
}

pub const SENTINEL: u32 = u32::MAX;
pub const LOG_TARGET: &str = "runtime::evm::polkavm";

Expand Down Expand Up @@ -68,7 +87,6 @@ impl<'a, T: Config, H: PrecompileHandle> PreparedCall<'a, T, H> {
let mut module_config = polkavm::ModuleConfig::new();
module_config.set_page_size(PAGE_SIZE);
module_config.set_gas_metering(Some(polkavm::GasMeteringKind::Sync));
module_config.set_allow_sbrk(false);
let module =
polkavm::Module::new(&engine, &module_config, polkavm_code.into()).map_err(|err| {
log::debug!(target: LOG_TARGET, "failed to create polkavm module: {err:?}");
Expand Down Expand Up @@ -116,3 +134,94 @@ impl<'a, T: Config, H: PrecompileHandle> PreparedCall<'a, T, H> {
exec_result
}
}

#[cfg(test)]
mod tests {
use super::*;
use polkavm::program::InstructionSetKind as Isa;

/// Smallest blob `ProgramBlob::parse` accepts, with the ISA we want to declare.
///
/// Hand-built rather than linked from a guest program: the ISA lives in one header
/// byte, so a real riscv64 contract would add a toolchain dependency and prove
/// nothing extra about the check under test.
fn blob_with_isa(isa: Isa) -> Vec<u8> {
let version: u8 = match isa {
Isa::ReviveV1 => 0,
Isa::Latest32 => 1,
Isa::Latest64 => 2,
Isa::JamV1 => 3,
};

// jump_table_entry_count, jump_table_entry_size, code_length, code, bitmask.
// The bitmask is ceil(code_len / 8) bytes and marks instruction boundaries.
let code_section: Vec<u8> = vec![0, 0, 1, 0, 0b0000_0001];
let mut body = vec![6u8]; // SECTION_CODE_AND_JUMP_TABLE
body.push(code_section.len() as u8);
body.extend_from_slice(&code_section);
body.push(0u8); // SECTION_END_OF_FILE

// magic + version + u64 length-of-whole-blob + body
let total = (4 + 1 + 8 + body.len()) as u64;
let mut blob = vec![b'P', b'V', b'M', 0u8, version];
blob.extend_from_slice(&total.to_le_bytes());
blob.extend_from_slice(&body);
blob
}

/// The blobs the test feeds the check must actually declare the ISA asked for,
/// otherwise the cases below would pass by accident.
#[test]
fn fixture_declares_the_requested_isa() {
for isa in [Isa::ReviveV1, Isa::JamV1, Isa::Latest32, Isa::Latest64] {
let parsed = polkavm::ProgramBlob::parse(blob_with_isa(isa).into())
.expect("hand-built blob should parse");
assert_eq!(parsed.isa(), isa);
}
}

/// This is the guarantee `ModuleConfig::set_allow_sbrk(false)` used to provide: no
/// contract may grow its heap. `Latest32`/`Latest64` carry the `sbrk` opcode, so
/// accepting them would silently restore what the old knob forbade.
#[test]
fn only_sbrk_free_instruction_sets_are_accepted() {
assert!(is_accepted_isa(Isa::ReviveV1));
assert!(is_accepted_isa(Isa::JamV1));
assert!(!is_accepted_isa(Isa::Latest32));
assert!(!is_accepted_isa(Isa::Latest64));
}

/// The prefix test must tolerate short and empty code.
///
/// `AccountCodes::get` returns an empty `Vec` for every address without contract
/// code, and the precompile set consults it on each call, so `code[0..8]` panics on
/// the most ordinary path there is: a transfer to a plain account.
#[test]
fn prefix_check_tolerates_code_shorter_than_the_prefix() {
let matches = |code: &[u8]| code.get(0..8) == Some(&PREFIX[..]);

assert!(!matches(&[]));
assert!(!matches(&PREFIX[..7]));
assert!(matches(&PREFIX));

let mut with_body = PREFIX.to_vec();
with_body.extend_from_slice(b"blob");
assert!(matches(&with_body));

let mut wrong = PREFIX;
wrong[0] = 0x00;
assert!(!matches(&wrong));
}

/// A blob that does not parse must be rejected before the ISA is consulted — the
/// deploy path calls `parse` first and maps the failure to its own error.
#[test]
fn malformed_blobs_do_not_parse() {
assert!(polkavm::ProgramBlob::parse(vec![].into()).is_err());
assert!(polkavm::ProgramBlob::parse(b"PVM\0".to_vec().into()).is_err());
// Right shape, unknown ISA version byte.
let mut bad = blob_with_isa(Isa::ReviveV1);
bad[4] = 99;
assert!(polkavm::ProgramBlob::parse(bad.into()).is_err());
}
}
Loading
Loading