Update on `cargo-pvm-contract`: a Rust SDK for smart contracts

Update on cargo-pvm-contract: a Rust SDK for smart contracts

cargo-pvm-contract is a Rust toolchain for writing smart contracts that compile to PVM and run on Polkadot through pallet-revive. The project has been in active development for a while and has reached a point where it’s worth sharing a public progress update.

What it is

Two authoring flavours, same host ABI:

  • macro - annotated Rust (#[contract], #[method], Lazy<T>, Mapping<K, V>) that derives dispatch, storage layout, and ABI encoding at compile time. The higher-level option, easier to start with.
  • DSL - a thinner abstraction. You write the call() entry point yourself and chain methods onto a ContractBuilder; storage is bare host.set_storage / get_storage with hand-computed keys. Produces noticeably smaller bytecode once the contract has real surface area.

The choice is about how much plumbing is spelled out by hand vs. generated.

Both flavours are ABI- and storage-compatible with Solidity: function selectors and calldata encoding match solc, and storage uses the same keccak-derived slot keys and packed-field layout as Solidity contracts. The SDK makes that compatibility automatic, with concrete consequences:

  • Ethereum tooling works unchanged. Libraries like ethers, viem, and the cast CLI deploy, call, and read SDK contracts - same selectors and storage slots.
  • Cross-contract calls between Solidity and Rust work directly, and because the wire format and slot derivation match, an EIP-1967 proxy should be able to swap a Solidity implementation for a Rust one behind the same address with no adapter.

A token in the macro flavour, sketched to show the shape:


#[pvm_contract_sdk::contract(allocator = "bump")]

mod my_token {

    use pvm_contract_sdk::{Address, HostApi, Lazy, Mapping, U256};

    #[derive(pvm_contract_sdk::SolError)]

    pub struct InsufficientBalance;

    pub struct MyToken {

        total_supply: Lazy<U256>,

        balances: Mapping<Address, U256>,

    }

    impl MyToken {

        #[pvm_contract_sdk::method]

        pub fn balance_of(&self, account: Address) -> U256 {

            self.balances.get(&account)

        }

        #[pvm_contract_sdk::method]

        pub fn transfer(&mut self, to: Address, amount: U256) -> Result<(), InsufficientBalance> {

            let from = self.caller();

            let balance = self.balances.get(&from);

            if balance < amount {

                return Err(InsufficientBalance);

            }

            self.balances.insert(&from, &(balance - amount));

            self.balances.insert(&to, &(self.balances.get(&to) + amount));

            Ok(())

        }

        fn caller(&self) -> Address {

            let mut caller = [0u8; 20];

            self.host().caller(&mut caller);

            Address(caller)

        }

    }

}

Why bytecode size matters on PVM

PVM bytecode is structurally larger than EVM bytecode, and every byte of contract code is charged as PoV (proof size) on every call. Smaller contracts aren’t a cosmetic property - they’re a per-call discount that compounds for the lifetime of the contract.

Across our benchmark suite, contracts built with cargo-pvm-contract are competitive with the same contracts compiled from Solidity to PVM. The numbers below are an early checkpoint from one benchmark run, not guarantees - they’ll move as the SDK matures - but the broad picture is consistent:

  • Rust SDK DSL is the smallest PVM implementation on every token-shaped contract - roughly a third smaller than Solidity (PVM) on the ERC-20/721/1155 benchmarks.

  • Rust SDK macro lands in the same ballpark as Solidity (PVM) on token-shaped contracts - within roughly ±15% either way - and is expected to stay around it as more features land.

Benchmark provenance: Rust SDK from cargo-pvm-contract main at commit 2eec1fe; Solidity (PVM) compiled with the resolc new_york branch and Solidity (EVM) with solc 0.8.30. Numbers are from a single run on 2026-06-16 and will shift as the SDK and toolchain evolve.

Status: early preview

This is not a 1.0. Several things contracts will eventually want are still being wired up: deeper storage composition, a built-in reentrancy guard, and event-ergonomics polish. Bytecode sizes will move up as those land - the relative position vs. Solidity (PVM) should hold, but absolute numbers are an early checkpoint.

Where to look

The SDK is being built in the open. Issues, PRs, and API feedback are all welcome - the surface stabilises over the next few months and input from real contract authors is more useful now than later.

You have now found the reason why PVM is a bad idea, as we have already known a year ago: On Ethereum and EVM compatibility

By the way, if you just switch from PVM to standard RISC-V, you’ll immediately gain 20% code size reduction. This is how bad PVM is (and also why you had to deploy EVM alongside PVM in Polkadot Hub).

Hey Wei, how is your compatibility hub going? Can’t wait to see it live on Polkadot to completely crush revive on security on performance. Or is it still in the design phase?

We didn’t spend much time on that since the final update. The issue we had is the same as all other teams leaving Polkadot.

Honestly, most teams will have much better time forking any of the “products” that you developed, deploying to Ethereum or Monad, than helping you to develop it on Polkadot Hub. This is especially considering the performance and binary size issues of PVM. So we just put it there.

We hate to derail the thread on unrelated issues but this is what you’re starting.

So you’ve tried nothing and nothing worked. Whats the point of leaving sour comments on real achievements on a real, deployed and working solution?

I congratulate @smiasojed and the Team. This is a fantastic achievement. Please don’t let yourself get frustrated because of bad faith comments. Keep it up!

You know that up to today, Moonbeam still has more users than Polkadot Hub, right?

Comically, Parity is the team who refused to listen, and then spent multiple years to develop a custom solution, and now… still after 6 months of your product launch, there’s still less than a few thousand contracts on the network.

We aren’t the guys leaving sour comments. We pointed out that your PVM solution is bad and we already pointed it out a year ago. We have a technical argument and we won’t be shy to share it.

The main purpose of this post is to announce the Rust SDK. Regarding PVM vs EVM on the numbers:

  • Per-call PoV: Rust SDK DSL beats native EVM on ERC-721 mint (−22%) and ERC-1155 create (−9%); within a few % of EVM on simpler workloads.
  • Per-call ref_time: ~5–16% over EVM today in interpreter mode on token-style workloads. JIT lands on PVM soon and closes most of it.
  • Deploy: single-digit % over EVM on token contracts, paid once.

Bytecode size is the one axis where PVM structurally costs more than EVM - that’s why the SDK focuses on shrinking it. On the per-call axes the SDK already lands competitive or ahead, and the residual ref_time gap is an interpreter-overhead artefact closing with JIT.